diff options
57 files changed, 905 insertions, 314 deletions
diff --git a/client/mysql.cc b/client/mysql.cc index 99b8499faea..134f54757cb 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -4091,7 +4091,7 @@ static int com_source(String *buffer, char *line) If we got an error during source operation, don't abort the client if ignore_errors is set */ - if (error && !batch_abort_on_error && ignore_errors) + if (error && ignore_errors) error= -1; // Ignore error return error; } diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 635d013eae8..be4a3e06fbb 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -1255,15 +1255,6 @@ void die(const char *fmt, ...) DBUG_ENTER("die"); DBUG_PRINT("enter", ("start_lineno: %d", start_lineno)); - /* - Protect against dying twice - first time 'die' is called, try to write log files - second time, just exit - */ - if (dying) - cleanup_and_exit(1); - dying= 1; - /* Print the error message */ fprintf(stderr, "mysqltest: "); if (cur_file && cur_file != file_stack) @@ -1282,6 +1273,15 @@ void die(const char *fmt, ...) fprintf(stderr, "\n"); fflush(stderr); + /* + Protect against dying twice + first time 'die' is called, try to write log files + second time, just exit + */ + if (dying) + cleanup_and_exit(1); + dying= 1; + log_file.show_tail(opt_tail_lines); /* @@ -1876,7 +1876,7 @@ void check_result() if (access(reject_file, W_OK) == 0) { /* Result file directory is writable, save reject file there */ - fn_format(reject_file, result_file_name, NULL, + fn_format(reject_file, result_file_name, "", ".reject", MY_REPLACE_EXT); } else diff --git a/dbug/dbug.c b/dbug/dbug.c index 59f7f4e10c5..608b4395c81 100644 --- a/dbug/dbug.c +++ b/dbug/dbug.c @@ -286,7 +286,7 @@ typedef struct _db_code_state_ { #define ListDel(A,B,C) ListAddDel(A,B,C,EXCLUDE) static struct link *ListAddDel(struct link *, const char *, const char *, int); static struct link *ListCopy(struct link *); -static int InList(struct link *linkp,const char *cp); +static int InList(struct link *linkp,const char *cp, int exact_match); static uint ListFlags(struct link *linkp); static void FreeList(struct link *linkp); @@ -1581,13 +1581,13 @@ static struct link *ListCopy(struct link *orig) * */ -static int InList(struct link *linkp, const char *cp) +static int InList(struct link *linkp, const char *cp, int exact_match) { int result; for (result=MATCHED; linkp != NULL; linkp= linkp->next_link) { - if (!fnmatch(linkp->str, cp, 0)) + if (!(exact_match ? strcmp(linkp->str,cp) : fnmatch(linkp->str, cp, 0))) return linkp->flags; if (!(linkp->flags & EXCLUDE)) result=NOT_MATCHED; @@ -1752,8 +1752,8 @@ void _db_end_() static int DoTrace(CODE_STATE *cs) { if ((cs->stack->maxdepth == 0 || cs->level <= cs->stack->maxdepth) && - InList(cs->stack->processes, cs->process) & (MATCHED|INCLUDE)) - switch(InList(cs->stack->functions, cs->func)) { + InList(cs->stack->processes, cs->process, 0) & (MATCHED|INCLUDE)) + switch(InList(cs->stack->functions, cs->func, 0)) { case INCLUDE|SUBDIR: return ENABLE_TRACE; case INCLUDE: return DO_TRACE; case MATCHED|SUBDIR: @@ -1790,10 +1790,10 @@ static int DoTrace(CODE_STATE *cs) #ifndef THREAD static BOOLEAN DoProfile(CODE_STATE *cs) { - return PROFILING && - cs->level <= cs->stack->maxdepth && - InList(cs->stack->p_functions, cs->func) & (INCLUDE|MATCHED) && - InList(cs->stack->processes, cs->process) & (INCLUDE|MATCHED); + return (PROFILING && + cs->level <= cs->stack->maxdepth && + InList(cs->stack->p_functions, cs->func, 0) & (INCLUDE|MATCHED) && + InList(cs->stack->processes, cs->process, 0) & (INCLUDE|MATCHED)); } #endif @@ -1826,11 +1826,11 @@ FILE *_db_fp_(void) BOOLEAN _db_keyword_(CODE_STATE *cs, const char *keyword, int strict) { + int match= strict ? INCLUDE : INCLUDE|MATCHED; get_code_state_if_not_set_or_return FALSE; - strict=strict ? INCLUDE : INCLUDE|MATCHED; - return DEBUGGING && DoTrace(cs) & DO_TRACE && - InList(cs->stack->keywords, keyword) & strict; + return (DEBUGGING && DoTrace(cs) & DO_TRACE && + InList(cs->stack->keywords, keyword, strict) & match); } /* diff --git a/include/my_base.h b/include/my_base.h index 8065441d4d7..8e6436bde70 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -50,6 +50,7 @@ #define HA_OPEN_COPY 256 /* Open copy (for repair) */ /* Internal temp table, used for temporary results */ #define HA_OPEN_INTERNAL_TABLE 512 +#define HA_OPEN_MERGE_TABLE 1024 /* The following is parameter to ha_rkey() how to use key */ @@ -196,6 +197,7 @@ enum ha_extra_function { */ HA_EXTRA_ATTACH_CHILDREN, HA_EXTRA_DETACH_CHILDREN, + HA_EXTRA_DETACH_CHILD, /* Inform handler we will force a close as part of flush */ HA_EXTRA_PREPARE_FOR_FORCED_CLOSE }; diff --git a/include/thr_lock.h b/include/thr_lock.h index 14d93a47e5a..08cc8bd5408 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -82,6 +82,12 @@ enum enum_thr_lock_result { THR_LOCK_SUCCESS= 0, THR_LOCK_ABORTED= 1, THR_LOCK_WAIT_TIMEOUT= 2, THR_LOCK_DEADLOCK= 3 }; +/* Priority for locks */ +#define THR_LOCK_LATE_PRIV 1 /* For locks to be merged with org lock */ +#define THR_LOCK_MERGE_PRIV 2 /* For merge tables */ + +#define THR_UNLOCK_UPDATE_STATUS 1 + extern ulong max_write_lock_count; extern ulong table_lock_wait_timeout; extern my_bool thr_lock_inited; @@ -116,9 +122,10 @@ typedef struct st_thr_lock_data { struct st_thr_lock_data *next,**prev; struct st_thr_lock *lock; pthread_cond_t *cond; - enum thr_lock_type type; void *status_param; /* Param to status functions */ void *debug_print_param; + enum thr_lock_type type; + uint priority; } THR_LOCK_DATA; struct st_lock_list { @@ -138,8 +145,10 @@ typedef struct st_thr_lock { void (*get_status)(void*, my_bool); /* When one gets a lock */ void (*copy_status)(void*,void*); void (*update_status)(void*); /* Before release of write */ - void (*restore_status)(void*); /* Before release of read */ + void (*restore_status)(void*); /* Before release of read */ + my_bool (*start_trans)(void*); /* When all locks are taken */ my_bool (*check_status)(void *); + void (*fix_status)(void *, void *);/* For thr_merge_locks() */ my_bool allow_multiple_concurrent_insert; } THR_LOCK; @@ -154,13 +163,11 @@ void thr_lock_init(THR_LOCK *lock); void thr_lock_delete(THR_LOCK *lock); void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *status_param); -enum enum_thr_lock_result thr_lock(THR_LOCK_DATA *data, - THR_LOCK_OWNER *owner, - enum thr_lock_type lock_type); -void thr_unlock(THR_LOCK_DATA *data); +void thr_unlock(THR_LOCK_DATA *data, uint unlock_flags); enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner); -void thr_multi_unlock(THR_LOCK_DATA **data,uint count); +void thr_multi_unlock(THR_LOCK_DATA **data,uint count, uint unlock_flags); +void thr_merge_locks(THR_LOCK_DATA **data, uint org_count, uint new_count); void thr_abort_locks(THR_LOCK *lock, my_bool upgrade_lock); my_bool thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread); void thr_print_locks(void); /* For debugging */ diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 2eb32c33954..41501da6088 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -4281,6 +4281,7 @@ sub extract_warning_lines ($) { qr/Slave SQL thread retried transaction/, qr/Slave \(additional info\)/, qr/Incorrect information in file/, + qr/Incorrect key file for table .*crashed.*/, qr/Slave I\/O: Get master SERVER_ID failed with error:.*/, qr/Slave I\/O: Get master clock failed with error:.*/, qr/Slave I\/O: Get master COLLATION_SERVER failed with error:.*/, diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result index 8cd5302345b..2a36c78fc28 100644 --- a/mysql-test/r/merge.result +++ b/mysql-test/r/merge.result @@ -1574,7 +1574,7 @@ UNLOCK TABLES; # # Trigger on parent DELETE FROM t4 WHERE c1 = 4; -CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1; +CREATE TRIGGER t4_ai1 AFTER INSERT ON t4 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; @@ -1586,10 +1586,13 @@ c1 2 3 4 -DROP TRIGGER t4_ai; +DROP TRIGGER t4_ai1; +CHECK TABLE t3; +Table Op Msg_type Msg_text +test.t3 check status OK # Trigger on parent under LOCK TABLES LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; -CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1; +CREATE TRIGGER t4_ai2 AFTER INSERT ON t4 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; @@ -1602,12 +1605,15 @@ c1 3 4 4 -DROP TRIGGER t4_ai; +DROP TRIGGER t4_ai2; UNLOCK TABLES; +CHECK TABLE t3; +Table Op Msg_type Msg_text +test.t3 check status OK # # Trigger on child DELETE FROM t4 WHERE c1 = 4; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1; +CREATE TRIGGER t3_ai3 AFTER INSERT ON t3 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; @@ -1624,10 +1630,13 @@ c1 3 4 33 -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai3; +CHECK TABLE t3; +Table Op Msg_type Msg_text +test.t3 check status OK # Trigger on child under LOCK TABLES LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1; +CREATE TRIGGER t3_ai4 AFTER INSERT ON t3 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; @@ -1647,11 +1656,17 @@ c1 33 33 DELETE FROM t4 WHERE c1 = 33; -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai4; +CHECK TABLE t3; +Table Op Msg_type Msg_text +test.t3 check status OK # # Trigger with table use on child DELETE FROM t4 WHERE c1 = 4; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +CREATE TRIGGER t3_ai5 AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +SELECT COUNT(*) FROM t2; +COUNT(*) +1 INSERT INTO t4 VALUES (4); SELECT * FROM t4 ORDER BY c1; c1 @@ -1670,10 +1685,15 @@ c1 33 DELETE FROM t4 WHERE c1 = 22; DELETE FROM t4 WHERE c1 = 33; -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai5; +UNLOCK TABLES; +CHECK TABLE t2,t3; +Table Op Msg_type Msg_text +test.t2 check status OK +test.t3 check status OK # Trigger with table use on child under LOCK TABLES LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +CREATE TRIGGER t3_ai6 AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); INSERT INTO t4 VALUES (4); SELECT * FROM t4 ORDER BY c1; c1 @@ -1692,10 +1712,44 @@ c1 4 22 33 -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai6; +UNLOCK TABLES; +check table t2,t3,t4; +Table Op Msg_type Msg_text +test.t2 check status OK +test.t3 check status OK +test.t4 check status OK DELETE FROM t4 WHERE c1 = 22; DELETE FROM t4 WHERE c1 = 33; +# Trigger with table use on child under different LOCK TABLES +DELETE FROM t4 WHERE c1 = 4; +LOCK TABLES t4 WRITE,t3 WRITE, t2 WRITE, t1 WRITE; +CREATE TRIGGER t3_ai7 AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +INSERT INTO t4 VALUES (4); +SELECT * FROM t4 ORDER BY c1; +c1 +1 +2 +3 +4 +INSERT INTO t3 VALUES (33); +SELECT * FROM t4 ORDER BY c1; +c1 +1 +2 +3 +4 +22 +33 +DROP TRIGGER t3_ai7; UNLOCK TABLES; +check table t2,t3,t4; +Table Op Msg_type Msg_text +test.t2 check status OK +test.t3 check status OK +test.t4 check status OK +DELETE FROM t4 WHERE c1 = 22; +DELETE FROM t4 WHERE c1 = 33; # # Repair # @@ -1711,7 +1765,6 @@ c1 2 3 4 -4 LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; REPAIR TABLE t4; Table Op Msg_type Msg_text @@ -1725,7 +1778,6 @@ c1 2 3 4 -4 UNLOCK TABLES; # # Optimize @@ -1742,7 +1794,6 @@ c1 2 3 4 -4 LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; OPTIMIZE TABLE t4; Table Op Msg_type Msg_text @@ -1756,14 +1807,13 @@ c1 2 3 4 -4 UNLOCK TABLES; # # Checksum # CHECKSUM TABLE t4; Table Checksum -test.t4 46622073 +test.t4 149057747 CHECKSUM TABLE t2; Table Checksum test.t2 3700403066 @@ -1773,11 +1823,10 @@ c1 2 3 4 -4 LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; CHECKSUM TABLE t4; Table Checksum -test.t4 46622073 +test.t4 149057747 CHECKSUM TABLE t2; Table Checksum test.t2 3700403066 @@ -1787,7 +1836,6 @@ c1 2 3 4 -4 UNLOCK TABLES; # # Insert delayed @@ -1801,7 +1849,6 @@ c1 2 3 4 -4 33 LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; INSERT DELAYED INTO t4 VALUES(444); @@ -1814,7 +1861,6 @@ c1 2 3 4 -4 33 UNLOCK TABLES; DROP TABLE t1, t2, t3, t4; diff --git a/mysql-test/r/merge_debug.result b/mysql-test/r/merge_debug.result new file mode 100644 index 00000000000..b857ff597f2 --- /dev/null +++ b/mysql-test/r/merge_debug.result @@ -0,0 +1,24 @@ +set global storage_engine=myisam; +set session storage_engine=myisam; +drop table if exists crashed,t2,t3,t4; +SET @orig_debug=@@debug; +CREATE TABLE crashed (c1 INT); +CREATE TABLE t2 (c1 INT); +CREATE TABLE t3 (c1 INT); +CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(crashed,t2,t3) INSERT_METHOD=LAST; +INSERT INTO crashed VALUES (10); +INSERT INTO t2 VALUES (20); +INSERT INTO t3 VALUES (30); +LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, crashed WRITE; +SET GLOBAL debug="+d,*,myisam_pretend_crashed_table_on_open"; +CREATE TRIGGER t1_ai AFTER INSERT ON crashed FOR EACH ROW INSERT INTO t2 VALUES(29); +SET GLOBAL debug=@orig_debug; +INSERT INTO t4 VALUES (39); +ERROR HY000: Table 't4' was not locked with LOCK TABLES +INSERT INTO crashed VALUES (11); +ERROR HY000: Table 'crashed' was not locked with LOCK TABLES +INSERT INTO t2 VALUES (21); +INSERT INTO t3 VALUES (31); +UNLOCK TABLES; +DROP TRIGGER t1_ai; +DROP TABLE t4,crashed,t2,t3; diff --git a/mysql-test/r/udf_query_cache.result b/mysql-test/r/udf_query_cache.result index 01dac554142..8c25c127012 100644 --- a/mysql-test/r/udf_query_cache.result +++ b/mysql-test/r/udf_query_cache.result @@ -3,6 +3,7 @@ CREATE FUNCTION metaphon RETURNS STRING SONAME "UDF_EXAMPLE_LIB"; create table t1 (a char); set GLOBAL query_cache_size=1355776; reset query cache; +flush status; select metaphon('MySQL') from t1; metaphon('MySQL') show status like "Qcache_hits"; diff --git a/mysql-test/r/warnings_debug.result b/mysql-test/r/warnings_debug.result new file mode 100644 index 00000000000..08908bf0437 --- /dev/null +++ b/mysql-test/r/warnings_debug.result @@ -0,0 +1,10 @@ +drop table if exists t1; +create table t1 (a int primary key) engine=innodb; +SET SESSION debug="+d,warn_during_ha_commit_trans"; +INSERT INTO t1 VALUES (1); +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back +SHOW WARNINGS; +Level Code Message +Warning 1196 Some non-transactional changed tables couldn't be rolled back +drop table t1; diff --git a/mysql-test/suite/parts/r/partition_repair_myisam.result b/mysql-test/suite/parts/r/partition_repair_myisam.result index 2d0a26b397c..4af00ddcc6d 100644 --- a/mysql-test/suite/parts/r/partition_repair_myisam.result +++ b/mysql-test/suite/parts/r/partition_repair_myisam.result @@ -408,7 +408,7 @@ ALTER TABLE t1_will_crash CHECK PARTITION p6; Table Op Msg_type Msg_text test.t1_will_crash check warning Size of datafile is: 868 Should be: 604 test.t1_will_crash check error Record-count is not ok; is 8 Should be: 7 -test.t1_will_crash check warning Found 10 key parts. Should be: 7 +test.t1_will_crash check warning Found 10 parts. Should be: 7 test.t1_will_crash check error Partition p6 returned error test.t1_will_crash check error Corrupt ALTER TABLE t1_will_crash REPAIR PARTITION p6; diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test index df99d6150ab..4e0ca615f99 100644 --- a/mysql-test/t/merge.test +++ b/mysql-test/t/merge.test @@ -1119,35 +1119,38 @@ UNLOCK TABLES; --echo # --echo # Trigger on parent DELETE FROM t4 WHERE c1 = 4; -CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1; +CREATE TRIGGER t4_ai1 AFTER INSERT ON t4 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; SELECT * FROM t4 ORDER BY c1; -DROP TRIGGER t4_ai; +DROP TRIGGER t4_ai1; +CHECK TABLE t3; --echo # Trigger on parent under LOCK TABLES LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; -CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1; +CREATE TRIGGER t4_ai2 AFTER INSERT ON t4 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; SELECT * FROM t4 ORDER BY c1; -DROP TRIGGER t4_ai; +DROP TRIGGER t4_ai2; UNLOCK TABLES; +CHECK TABLE t3; --echo # --echo # Trigger on child DELETE FROM t4 WHERE c1 = 4; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1; +CREATE TRIGGER t3_ai3 AFTER INSERT ON t3 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; INSERT INTO t3 VALUES (33); SELECT @a; SELECT * FROM t4 ORDER BY c1; -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai3; +CHECK TABLE t3; --echo # Trigger on child under LOCK TABLES LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1; +CREATE TRIGGER t3_ai4 AFTER INSERT ON t3 FOR EACH ROW SET @a=1; SET @a=0; INSERT INTO t4 VALUES (4); SELECT @a; @@ -1155,29 +1158,47 @@ INSERT INTO t3 VALUES (33); SELECT @a; SELECT * FROM t4 ORDER BY c1; DELETE FROM t4 WHERE c1 = 33; -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai4; +CHECK TABLE t3; --echo # --echo # Trigger with table use on child DELETE FROM t4 WHERE c1 = 4; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +CREATE TRIGGER t3_ai5 AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +SELECT COUNT(*) FROM t2; INSERT INTO t4 VALUES (4); SELECT * FROM t4 ORDER BY c1; INSERT INTO t3 VALUES (33); SELECT * FROM t4 ORDER BY c1; DELETE FROM t4 WHERE c1 = 22; DELETE FROM t4 WHERE c1 = 33; -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai5; +UNLOCK TABLES; +CHECK TABLE t2,t3; --echo # Trigger with table use on child under LOCK TABLES LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE; -CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +CREATE TRIGGER t3_ai6 AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); INSERT INTO t4 VALUES (4); SELECT * FROM t4 ORDER BY c1; INSERT INTO t3 VALUES (33); SELECT * FROM t4 ORDER BY c1; -DROP TRIGGER t3_ai; +DROP TRIGGER t3_ai6; +UNLOCK TABLES; +check table t2,t3,t4; DELETE FROM t4 WHERE c1 = 22; DELETE FROM t4 WHERE c1 = 33; +--echo # Trigger with table use on child under different LOCK TABLES +DELETE FROM t4 WHERE c1 = 4; +LOCK TABLES t4 WRITE,t3 WRITE, t2 WRITE, t1 WRITE; +CREATE TRIGGER t3_ai7 AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22); +INSERT INTO t4 VALUES (4); +SELECT * FROM t4 ORDER BY c1; +INSERT INTO t3 VALUES (33); +SELECT * FROM t4 ORDER BY c1; +DROP TRIGGER t3_ai7; UNLOCK TABLES; +check table t2,t3,t4; +DELETE FROM t4 WHERE c1 = 22; +DELETE FROM t4 WHERE c1 = 33; # --echo # --echo # Repair diff --git a/mysql-test/t/merge_debug.test b/mysql-test/t/merge_debug.test new file mode 100644 index 00000000000..2e30cf88b5d --- /dev/null +++ b/mysql-test/t/merge_debug.test @@ -0,0 +1,42 @@ +# +# Test failures with MERGE +# + +--source include/have_debug.inc + +let $default=`select @@global.storage_engine`; +set global storage_engine=myisam; +set session storage_engine=myisam; + +--disable_warnings +drop table if exists crashed,t2,t3,t4; +--enable_warnings + +SET @orig_debug=@@debug; + +# +# Check that MariaDB handles reopen that fails without crashing +# +CREATE TABLE crashed (c1 INT); +CREATE TABLE t2 (c1 INT); +CREATE TABLE t3 (c1 INT); +CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(crashed,t2,t3) INSERT_METHOD=LAST; +INSERT INTO crashed VALUES (10); +INSERT INTO t2 VALUES (20); +INSERT INTO t3 VALUES (30); + +LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, crashed WRITE; +SET GLOBAL debug="+d,*,myisam_pretend_crashed_table_on_open"; +--disable_warnings +CREATE TRIGGER t1_ai AFTER INSERT ON crashed FOR EACH ROW INSERT INTO t2 VALUES(29); +--enable_warnings +SET GLOBAL debug=@orig_debug; +--error ER_TABLE_NOT_LOCKED +INSERT INTO t4 VALUES (39); +--error ER_TABLE_NOT_LOCKED +INSERT INTO crashed VALUES (11); +INSERT INTO t2 VALUES (21); +INSERT INTO t3 VALUES (31); +UNLOCK TABLES; +DROP TRIGGER t1_ai; +DROP TABLE t4,crashed,t2,t3; diff --git a/mysql-test/t/udf_query_cache.test b/mysql-test/t/udf_query_cache.test index ce7bd43ea1f..b0037973631 100644 --- a/mysql-test/t/udf_query_cache.test +++ b/mysql-test/t/udf_query_cache.test @@ -20,6 +20,7 @@ create table t1 (a char); set GLOBAL query_cache_size=1355776; reset query cache; +flush status; select metaphon('MySQL') from t1; show status like "Qcache_hits"; diff --git a/mysql-test/t/warnings_debug.test b/mysql-test/t/warnings_debug.test new file mode 100644 index 00000000000..99d02330960 --- /dev/null +++ b/mysql-test/t/warnings_debug.test @@ -0,0 +1,19 @@ +--source include/have_innodb.inc +--source include/have_debug.inc + +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1 (a int primary key) engine=innodb; + +# Test that warnings produced during autocommit (after calling +# set_ok_status()) are still reported to the client. +SET SESSION debug="+d,warn_during_ha_commit_trans"; +INSERT INTO t1 VALUES (1); +# The warning will be shown automatically by mysqltest; there was a bug where +# this didn't happen because the warning was not counted when sending result +# packet. Show the warnings manually also. +SHOW WARNINGS; + +drop table t1; diff --git a/mysys/my_getopt.c b/mysys/my_getopt.c index 21eea5517f1..db6ccd230f2 100644 --- a/mysys/my_getopt.c +++ b/mysys/my_getopt.c @@ -614,7 +614,6 @@ static int setval(const struct my_option *opts, void *value, char *argument, my_bool set_maximum_value) { int err= 0; - int pos; if (value && argument) { diff --git a/mysys/my_symlink2.c b/mysys/my_symlink2.c index 7c3ddbb911c..bc7ac751fad 100644 --- a/mysys/my_symlink2.c +++ b/mysys/my_symlink2.c @@ -34,8 +34,8 @@ File my_create_with_symlink(const char *linkname, const char *filename, char abs_linkname[FN_REFLEN]; DBUG_ENTER("my_create_with_symlink"); DBUG_PRINT("enter", ("linkname: %s filename: %s", - linkname ? linkname : "(null)", - filename ? filename : "(null)")); + linkname ? linkname : "(NULL)", + filename ? filename : "(NULL)")); if (my_disable_symlinks) { diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index b8aa9e5fcc0..341b2f0058e 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -63,6 +63,11 @@ update_status: A storage engine should also call update_status internally in the ::external_lock(F_UNLCK) method. In MyISAM and CSV this functions updates the length of the datafile. + MySQL does in some exceptional cases (when doing DLL statements on + open tables calls thr_unlock() followed by thr_lock() without calling + ::external_lock() in between. In this case thr_unlock() is called with + the THR_UNLOCK_UPDATE_STATUS flag and thr_unlock() will call + update_status for write locks. get_status: When one gets a lock this functions is called. In MyISAM this stores the number of rows and size of the datafile @@ -105,8 +110,30 @@ static inline pthread_cond_t *get_cond(void) return &my_thread_var->suspend; } + +/* + Priority for locks (decides in which order locks are locked) + We want all write locks to be first, followed by read locks. + Locks from MERGE tables has a little lower priority than other + locks, to allow one to release merge tables without having + to unlock and re-lock other locks. + The lower the number, the higher the priority for the lock. + Read locks should have 4, write locks should have 0. + UNLOCK is 8, to force these last in thr_merge_locks. + For MERGE tables we add 2 (THR_LOCK_MERGE_PRIV) to the lock priority. + THR_LOCK_LATE_PRIV (1) is used when one locks other tables to be merged + with existing locks. This way we prioritize the original locks over the + new locks. +*/ + +static uint lock_priority[(uint)TL_WRITE_ONLY+1] = +{ 8, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0}; + +#define LOCK_CMP(A,B) ((uchar*) ((A)->lock) + lock_priority[(uint) (A)->type] + (A)->priority < (uchar*) ((B)->lock) + lock_priority[(uint) (B)->type] + (B)->priority) + + /* -** For the future (now the thread specific cond is alloced by my_pthread.c) + For the future (now the thread specific cond is alloced by my_pthread.c) */ my_bool init_thr_lock() @@ -379,6 +406,7 @@ void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *param) data->owner= 0; /* no owner yet */ data->status_param=param; data->cond=0; + data->priority= 0; } @@ -530,7 +558,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, } -enum enum_thr_lock_result +static enum enum_thr_lock_result thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, enum thr_lock_type lock_type) { @@ -544,6 +572,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, data->cond=0; /* safety */ data->type=lock_type; data->owner= owner; /* Must be reset ! */ + data->priority&= ~THR_LOCK_LATE_PRIV; VOID(pthread_mutex_lock(&lock->mutex)); DBUG_PRINT("lock",("data: 0x%lx thread: 0x%lx lock: 0x%lx type: %d", (long) data, data->owner->info->thread_id, @@ -808,7 +837,7 @@ static inline void free_all_read_locks(THR_LOCK *lock, /* Unlock lock and free next thread on same lock */ -void thr_unlock(THR_LOCK_DATA *data) +void thr_unlock(THR_LOCK_DATA *data, uint unlock_flags) { THR_LOCK *lock=data->lock; enum thr_lock_type lock_type=data->type; @@ -832,6 +861,21 @@ void thr_unlock(THR_LOCK_DATA *data) } else lock->write.last=data->prev; + + if (unlock_flags & THR_UNLOCK_UPDATE_STATUS) + { + /* External lock was not called; Update or restore status */ + if (lock_type >= TL_WRITE_CONCURRENT_INSERT) + { + if (lock->update_status) + (*lock->update_status)(data->status_param); + } + else + { + if (lock->restore_status) + (*lock->restore_status)(data->status_param); + } + } if (lock_type == TL_READ_NO_INSERT) lock->read_no_write_count--; data->type=TL_UNLOCK; /* Mark unlocked */ @@ -967,14 +1011,12 @@ end: /* -** Get all locks in a specific order to avoid dead-locks -** Sort acording to lock position and put write_locks before read_locks if -** lock on same lock. + Get all locks in a specific order to avoid dead-locks + Sort acording to lock position and put write_locks before read_locks if + lock on same lock. Locks on MERGE tables has lower priority than other + locks of the same type. See comment for lock_priority. */ - -#define LOCK_CMP(A,B) ((uchar*) (A->lock) - (uint) ((A)->type) < (uchar*) (B->lock)- (uint) ((B)->type)) - static void sort_locks(THR_LOCK_DATA **data,uint count) { THR_LOCK_DATA **pos,**end,**prev,*tmp; @@ -999,18 +1041,22 @@ static void sort_locks(THR_LOCK_DATA **data,uint count) enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) { - THR_LOCK_DATA **pos,**end; + THR_LOCK_DATA **pos, **end, **first_lock; DBUG_ENTER("thr_multi_lock"); DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count)); + if (count > 1) sort_locks(data,count); + else if (count == 0) + DBUG_RETURN(THR_LOCK_SUCCESS); + /* lock everything */ for (pos=data,end=data+count; pos < end ; pos++) { enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type); if (result != THR_LOCK_SUCCESS) { /* Aborted */ - thr_multi_unlock(data,(uint) (pos-data)); + thr_multi_unlock(data,(uint) (pos-data), 0); DBUG_RETURN(result); } #ifdef MAIN @@ -1018,63 +1064,103 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) (long) pos[0]->lock, pos[0]->type); fflush(stdout); #endif } + /* - Ensure that all get_locks() have the same status + Call start_trans for all locks. If we lock the same table multiple times, we must use the same - status_param! + status_param; We ensure this by calling copy_status() for all + copies of the same tables. */ -#if !defined(DONT_USE_RW_LOCKS) - if (count > 1) + if ((*data)->lock->start_trans) + ((*data)->lock->start_trans)((*data)->status_param); + for (first_lock=data, pos= data+1 ; pos < end ; pos++) { - THR_LOCK_DATA *last_lock= end[-1]; - pos=end-1; - do + /* Get the current status (row count, checksum, trid etc) */ + if ((*pos)->lock->start_trans) + (*(*pos)->lock->start_trans)((*pos)->status_param); + /* + If same table as previous table use pointer to previous status + information to ensure that all read/write tables shares same + state. + */ + if (pos[0]->lock == pos[-1]->lock && pos[0]->lock->copy_status) + (pos[0]->lock->copy_status)((*pos)->status_param, + (*first_lock)->status_param); + else { - pos--; - if (last_lock->lock == (*pos)->lock && - last_lock->lock->copy_status) - { - if (last_lock->type <= TL_READ_NO_INSERT) - { - THR_LOCK_DATA **read_lock; - /* - If we are locking the same table with read locks we must ensure - that all tables share the status of the last write lock or - the same read lock. - */ - for (; - (*pos)->type <= TL_READ_NO_INSERT && - pos != data && - pos[-1]->lock == (*pos)->lock ; - pos--) ; - - read_lock = pos+1; - do - { - (last_lock->lock->copy_status)((*read_lock)->status_param, - (*pos)->status_param); - } while (*(read_lock++) != last_lock); - last_lock= (*pos); /* Point at last write lock */ - } - else - (*last_lock->lock->copy_status)((*pos)->status_param, - last_lock->status_param); - } - else - last_lock=(*pos); - } while (pos != data); + /* Different lock, use this as base for next lock */ + first_lock= pos; + } } -#endif DBUG_RETURN(THR_LOCK_SUCCESS); } - /* free all locks */ -void thr_multi_unlock(THR_LOCK_DATA **data,uint count) +/** + Merge two sets of locks. + + @param data All locks. First old locks, then new locks. + @param old_count Original number of locks. These are first in 'data'. + @param new_count How many new locks + + The merge is needed if the new locks contains same tables as the old + locks, in which case we have to ensure that same tables shares the + same status (as after a thr_multi_lock()). +*/ + +void thr_merge_locks(THR_LOCK_DATA **data, uint old_count, uint new_count) +{ + THR_LOCK_DATA **pos, **end, **first_lock= 0; + DBUG_ENTER("thr_merge_lock"); + + /* Remove marks on old locks to make them sort before new ones */ + for (pos=data, end= pos + old_count; pos < end ; pos++) + (*pos)->priority&= ~THR_LOCK_LATE_PRIV; + + /* Mark new locks with LATE_PRIV to make them sort after org ones */ + for (pos=data + old_count, end= pos + new_count; pos < end ; pos++) + (*pos)->priority|= THR_LOCK_LATE_PRIV; + + sort_locks(data, old_count + new_count); + + for (pos=data ; pos < end ; pos++) + { + /* Check if lock was unlocked before */ + if (pos[0]->type == TL_UNLOCK || ! pos[0]->lock->fix_status) + { + DBUG_PRINT("info", ("lock skipped. unlocked: %d fix_status: %d", + pos[0]->type == TL_UNLOCK, + pos[0]->lock->fix_status == 0)); + continue; + } + + /* + If same table as previous table use pointer to previous status + information to ensure that all read/write tables shares same + state. + */ + if (first_lock && pos[0]->lock == first_lock[0]->lock) + (pos[0]->lock->fix_status)((*first_lock)->status_param, + (*pos)->status_param); + else + { + /* Different lock, use this as base for next lock */ + first_lock= pos; + (pos[0]->lock->fix_status)((*first_lock)->status_param, 0); + } + } + DBUG_VOID_RETURN; +} + + +/* Unlock all locks */ + +void thr_multi_unlock(THR_LOCK_DATA **data,uint count, uint unlock_flags) { THR_LOCK_DATA **pos,**end; DBUG_ENTER("thr_multi_unlock"); - DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count)); + DBUG_PRINT("lock",("data: 0x%lx count: %d flags: %u", (long) data, count, + unlock_flags)); for (pos=data,end=data+count; pos < end ; pos++) { @@ -1084,7 +1170,7 @@ void thr_multi_unlock(THR_LOCK_DATA **data,uint count) fflush(stdout); #endif if ((*pos)->type != TL_UNLOCK) - thr_unlock(*pos); + thr_unlock(*pos, unlock_flags); else { DBUG_PRINT("lock",("Free lock: data: 0x%lx thread: 0x%lx lock: 0x%lx", @@ -1400,6 +1486,7 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, enum thr_lock_type new_lock_type) { THR_LOCK *lock=data->lock; + enum enum_thr_lock_result res; DBUG_ENTER("thr_upgrade_write_delay_lock"); pthread_mutex_lock(&lock->mutex); @@ -1420,6 +1507,8 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, if (lock->get_status) (*lock->get_status)(data->status_param, 0); pthread_mutex_unlock(&lock->mutex); + if (lock->start_trans) + (*lock->start_trans)(data->status_param); DBUG_RETURN(0); } @@ -1440,7 +1529,10 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, { check_locks(lock,"waiting for lock",0); } - DBUG_RETURN(wait_for_lock(&lock->write_wait,data,1)); + res= wait_for_lock(&lock->write_wait,data,1); + if (res == THR_LOCK_SUCCESS && lock->start_trans) + DBUG_RETURN((*lock->start_trans)(data->status_param)); + DBUG_RETURN(0); } @@ -1684,7 +1776,7 @@ static void *test_thread(void *arg) } } pthread_mutex_unlock(&LOCK_thread_count); - thr_multi_unlock(multi_locks,lock_counts[param]); + thr_multi_unlock(multi_locks,lock_counts[param], THR_UNLOCK_UPDATE_STATUS); } printf("Thread %s (%d) ended\n",my_thread_name(),param); fflush(stdout); diff --git a/sql/handler.cc b/sql/handler.cc index 1fcd9555221..c931b689842 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1094,6 +1094,12 @@ int ha_commit_trans(THD *thd, bool all) my_xid xid= thd->transaction.xid_state.xid.get_my_xid(); DBUG_ENTER("ha_commit_trans"); + /* Just a random warning to test warnings pushed during autocommit. */ + DBUG_EXECUTE_IF("warn_during_ha_commit_trans", + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARNING_NOT_COMPLETE_ROLLBACK, + ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));); + /* We must not commit the normal transaction if a statement transaction is pending. Otherwise statement transaction @@ -2643,8 +2649,18 @@ void handler::print_keydup_error(uint key_nr, const char *msg) - table->s->path - table->alias */ + +#ifndef DBUG_OFF +#define SET_FATAL_ERROR fatal_error=1 +#else +#define SET_FATAL_ERROR +#endif + void handler::print_error(int error, myf errflag) { +#ifndef DBUG_OFF + bool fatal_error= 0; +#endif DBUG_ENTER("handler::print_error"); DBUG_PRINT("enter",("error: %d",error)); @@ -2662,6 +2678,13 @@ void handler::print_error(int error, myf errflag) case HA_ERR_KEY_NOT_FOUND: case HA_ERR_NO_ACTIVE_RECORD: case HA_ERR_END_OF_FILE: + /* + This errors is not not normally fatal (for example for reads). However + if you get it during an update or delete, then its fatal. + As the user is calling print_error() (which is not done on read), we + assume something when wrong with the update or delete. + */ + SET_FATAL_ERROR; textno=ER_KEY_NOT_FOUND; break; case HA_ERR_WRONG_MRG_TABLE_DEF: @@ -2713,21 +2736,26 @@ void handler::print_error(int error, myf errflag) textno=ER_DUP_UNIQUE; break; case HA_ERR_RECORD_CHANGED: + SET_FATAL_ERROR; textno=ER_CHECKREAD; break; case HA_ERR_CRASHED: + SET_FATAL_ERROR; textno=ER_NOT_KEYFILE; break; case HA_ERR_WRONG_IN_RECORD: + SET_FATAL_ERROR; textno= ER_CRASHED_ON_USAGE; break; case HA_ERR_CRASHED_ON_USAGE: + SET_FATAL_ERROR; textno=ER_CRASHED_ON_USAGE; break; case HA_ERR_NOT_A_TABLE: textno= error; break; case HA_ERR_CRASHED_ON_REPAIR: + SET_FATAL_ERROR; textno=ER_CRASHED_ON_REPAIR; break; case HA_ERR_OUT_OF_MEM: @@ -2826,7 +2854,10 @@ void handler::print_error(int error, myf errflag) if (temporary) my_error(ER_GET_TEMPORARY_ERRMSG, MYF(0), error, str.ptr(), engine); else + { + SET_FATAL_ERROR; my_error(ER_GET_ERRMSG, MYF(0), error, str.ptr(), engine); + } } else my_error(ER_GET_ERRNO,errflag,error); @@ -2834,6 +2865,7 @@ void handler::print_error(int error, myf errflag) } } my_error(textno, errflag, table_share->table_name.str, error); + DBUG_ASSERT(!fatal_error || !debug_assert_if_crashed_table); DBUG_VOID_RETURN; } diff --git a/sql/lock.cc b/sql/lock.cc index 1908e68dbd2..566275c5ea2 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -211,7 +211,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, for (;;) { if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS, - &write_lock_used))) + &write_lock_used)) || + ! sql_lock->table_count) break; if (global_read_lock && write_lock_used && @@ -257,8 +258,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, thd_proc_info(thd, "System lock"); DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info)); - if (sql_lock->table_count && lock_external(thd, sql_lock->table, - sql_lock->table_count)) + if (lock_external(thd, sql_lock->table, sql_lock->table_count)) { /* Clear the lock type of all lock data to avoid reusage. */ reset_lock_data(sql_lock); @@ -279,8 +279,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, thd->lock_id)]; if (rc > 1) /* a timeout or a deadlock */ { - if (sql_lock->table_count) - VOID(unlock_external(thd, sql_lock->table, sql_lock->table_count)); + VOID(unlock_external(thd, sql_lock->table, sql_lock->table_count)); my_error(rc, MYF(0)); my_free((uchar*) sql_lock,MYF(0)); sql_lock= 0; @@ -388,7 +387,7 @@ void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock) if (sql_lock->table_count) VOID(unlock_external(thd,sql_lock->table,sql_lock->table_count)); if (sql_lock->lock_count) - thr_multi_unlock(sql_lock->locks,sql_lock->lock_count); + thr_multi_unlock(sql_lock->locks,sql_lock->lock_count, 0); my_free((uchar*) sql_lock,MYF(0)); DBUG_VOID_RETURN; } @@ -418,25 +417,8 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) uint i,found; DBUG_ENTER("mysql_unlock_read_tables"); - /* Move all write locks first */ - THR_LOCK_DATA **lock=sql_lock->locks; - for (i=found=0 ; i < sql_lock->lock_count ; i++) - { - if (sql_lock->locks[i]->type >= TL_WRITE_ALLOW_READ) - { - swap_variables(THR_LOCK_DATA *, *lock, sql_lock->locks[i]); - lock++; - found++; - } - } - /* unlock the read locked tables */ - if (i != found) - { - thr_multi_unlock(lock,i-found); - sql_lock->lock_count= found; - } + /* Call external lock for all tables to be unlocked */ - /* Then do the same for the external locks */ /* Move all write locked tables first */ TABLE **table=sql_lock->table; for (i=found=0 ; i < sql_lock->table_count ; i++) @@ -455,6 +437,27 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) VOID(unlock_external(thd,table,i-found)); sql_lock->table_count=found; } + + /* Call thr_unlock() for all tables to be unlocked */ + + /* Move all write locks first */ + THR_LOCK_DATA **lock=sql_lock->locks; + for (i=found=0 ; i < sql_lock->lock_count ; i++) + { + if (sql_lock->locks[i]->type >= TL_WRITE_ALLOW_READ) + { + swap_variables(THR_LOCK_DATA *, *lock, sql_lock->locks[i]); + lock++; + found++; + } + } + /* unlock the read locked tables */ + if (i != found) + { + thr_multi_unlock(lock, i-found, 0); + sql_lock->lock_count= found; + } + /* Fix the lock positions in TABLE */ table= sql_lock->table; found= 0; @@ -582,8 +585,21 @@ void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock) if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK, &write_lock_used))) { - for (uint i=0; i < locked->lock_count; i++) - thr_abort_locks(locked->locks[i]->lock, upgrade_lock); + if (table->children_attached) + { + /* + Don't abort locks for underlying tables just because merge table + is deleted. Doing would cause anyone accessing these tables to + spin in open_table/close_table forever until lock is released. + */ + thr_multi_unlock(locked->locks, locked->lock_count, + THR_UNLOCK_UPDATE_STATUS); + } + else + { + for (uint i=0; i < locked->lock_count; i++) + thr_abort_locks(locked->locks[i]->lock, upgrade_lock); + } my_free((uchar*) locked,MYF(0)); } DBUG_VOID_RETURN; @@ -624,21 +640,36 @@ bool mysql_lock_abort_for_thread(THD *thd, TABLE *table) } +/** + Merge two thr_lock:s + mysql_lock_merge() + + @param a Original locks + @param b New locks + + @retval New lock structure that contains a and b + + @note + a and b are freed with my_free() +*/ + MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b) { MYSQL_LOCK *sql_lock; TABLE **table, **end_table; DBUG_ENTER("mysql_lock_merge"); + DBUG_PRINT("enter", ("a->lock_count: %u b->lock_count: %u", + a->lock_count, b->lock_count)); if (!(sql_lock= (MYSQL_LOCK*) my_malloc(sizeof(*sql_lock)+ - sizeof(THR_LOCK_DATA*)*(a->lock_count+b->lock_count)+ + sizeof(THR_LOCK_DATA*)*((a->lock_count+b->lock_count)*2) + sizeof(TABLE*)*(a->table_count+b->table_count),MYF(MY_WME)))) DBUG_RETURN(0); // Fatal error sql_lock->lock_count=a->lock_count+b->lock_count; sql_lock->table_count=a->table_count+b->table_count; sql_lock->locks=(THR_LOCK_DATA**) (sql_lock+1); - sql_lock->table=(TABLE**) (sql_lock->locks+sql_lock->lock_count); + sql_lock->table=(TABLE**) (sql_lock->locks+sql_lock->lock_count*2); memcpy(sql_lock->locks,a->locks,a->lock_count*sizeof(*a->locks)); memcpy(sql_lock->locks+a->lock_count,b->locks, b->lock_count*sizeof(*b->locks)); @@ -659,6 +690,18 @@ MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b) (*table)->lock_data_start+= a->lock_count; } + /* + Ensure that locks of the same tables share same data structures if we + reopen a table that is already open. This can happen for example with + MERGE tables. + */ + + /* Copy the lock data array. thr_merge_lock() reorders its content */ + memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks, + sql_lock->lock_count * sizeof(*sql_lock->locks)); + thr_merge_locks(sql_lock->locks + sql_lock->lock_count, + a->lock_count, b->lock_count); + /* Delete old, not needed locks */ my_free((uchar*) a,MYF(0)); my_free((uchar*) b,MYF(0)); @@ -832,7 +875,7 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, /* Allocating twice the number of pointers for lock data for use in - thr_mulit_lock(). This function reorders the lock data, but cannot + thr_multi_lock(). This function reorders the lock data, but cannot update the table values. So the second part of the array is copied from the first part immediately before calling thr_multi_lock(). */ @@ -1062,11 +1105,13 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use) void unlock_table_name(THD *thd, TABLE_LIST *table_list) { + DBUG_ENTER("unlock_table_name"); if (table_list->table) { hash_delete(&open_cache, (uchar*) table_list->table); broadcast_refresh(); } + DBUG_VOID_RETURN; } diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 255e22005d3..5fc9eadeff3 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2057,7 +2057,7 @@ extern ulong slave_exec_mode_options; extern my_bool opt_readonly, lower_case_file_system; extern my_bool opt_userstat_running; extern my_bool opt_enable_named_pipe, opt_sync_frm, opt_allow_suspicious_udfs; -extern my_bool opt_secure_auth; +extern my_bool opt_secure_auth, debug_assert_if_crashed_table; extern char* opt_secure_file_priv; extern my_bool opt_log_slow_admin_statements, opt_log_slow_slave_statements; extern my_bool sp_automatic_privileges, opt_noacl; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index f8da9432732..daf2e236601 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -488,7 +488,7 @@ static pthread_cond_t COND_thread_cache, COND_flush_thread_cache; /* Global variables */ bool opt_update_log, opt_bin_log, opt_ignore_builtin_innodb= 0; -my_bool opt_log, opt_slow_log; +my_bool opt_log, opt_slow_log, debug_assert_if_crashed_table; my_bool opt_userstat_running; ulong log_output_options; my_bool opt_log_queries_not_using_indexes= 0; @@ -6037,7 +6037,7 @@ enum options_mysqld OPT_SECURE_FILE_PRIV, OPT_MIN_EXAMINED_ROW_LIMIT, OPT_LOG_SLOW_SLAVE_STATEMENTS, - OPT_DEBUG_CRC, OPT_DEBUG_ON, OPT_OLD_MODE, + OPT_DEBUG_CRC, OPT_DEBUG_ON, OPT_DEBUG_ASSERT_IF_CRASHED_TABLE, OPT_OLD_MODE, OPT_TEST_IGNORE_WRONG_OPTIONS, OPT_TEST_RESTART, #if defined(ENABLED_DEBUG_SYNC) OPT_DEBUG_SYNC_TIMEOUT, @@ -6207,6 +6207,10 @@ struct my_option my_long_options[] = 0, GET_ULONG, REQUIRED_ARG, 0, 0, ~(ulong) 0L, 0, 0, 0}, {"debug-flush", OPT_DEBUG_FLUSH, "Default debug log with flush after write", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-assert-if-crashed-table", OPT_DEBUG_ASSERT_IF_CRASHED_TABLE, + "Do an assert in handler::print_error() if we get a crashed table", + &debug_assert_if_crashed_table, &debug_assert_if_crashed_table, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, #endif {"default-character-set", OPT_DEFAULT_CHARACTER_SET_OLD, "Set the default character set (deprecated option, use --character-set-server instead).", diff --git a/sql/parse_file.cc b/sql/parse_file.cc index 3d65fa1de31..5bc16e55ec0 100644 --- a/sql/parse_file.cc +++ b/sql/parse_file.cc @@ -216,7 +216,7 @@ sql_create_definition_file(const LEX_STRING *dir, const LEX_STRING *file_name, File_option *param; DBUG_ENTER("sql_create_definition_file"); DBUG_PRINT("enter", ("Dir: %s, file: %s, base 0x%lx", - dir ? dir->str : "(null)", + dir ? dir->str : "", file_name->str, (ulong) base)); if (dir) diff --git a/sql/protocol.cc b/sql/protocol.cc index 786d6ef8544..8a9d712077d 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -203,7 +203,7 @@ net_send_ok(THD *thd, NET *net= &thd->net; uchar buff[MYSQL_ERRMSG_SIZE+10],*pos; bool error= FALSE; - DBUG_ENTER("my_ok"); + DBUG_ENTER("net_send_ok"); if (! net->vio) // hack for re-parsing queries { diff --git a/sql/set_var.cc b/sql/set_var.cc index 0eaa450d09c..7e00ae5aa19 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -4369,7 +4369,7 @@ bool sys_var_thd_dbug::update(THD *thd, set_var *var) uchar *sys_var_thd_dbug::value_ptr(THD *thd, enum_var_type type, LEX_STRING *b) { - char buf[256]; + char buf[1024]; if (type == OPT_GLOBAL) { DBUG_EXPLAIN_INITIAL(buf, sizeof(buf)); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e8dcc557f05..e14c8961165 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2045,6 +2045,8 @@ static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp) Remove parent from open_tables list and close it. This includes detaching and hence clearing parent references. */ + DBUG_PRINT("info", ("Closing parent to '%s'.'%s'", + table->s->db.str, table->s->table_name.str)); close_thread_table(thd, prv_p); } } @@ -3076,8 +3078,9 @@ bool reopen_table(TABLE *table) TABLE_LIST table_list; THD *thd= table->in_use; DBUG_ENTER("reopen_table"); - DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); + DBUG_PRINT("tcache", ("table: '%s'.'%s' table: 0x%lx share: 0x%lx", + table->s->db.str, table->s->table_name.str, + (long) table, (long) table->s)); DBUG_ASSERT(table->s->ref_count == 0); DBUG_ASSERT(!table->sort.io_cache); @@ -3364,7 +3367,8 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) merge_table_found= TRUE; if (!tables || (!db_stat && reopen_table(table))) { - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); + my_error(ER_CANT_REOPEN_TABLE, MYF(0), + table->alias ? table->alias : table->s->table_name.str); /* If we could not allocate 'tables', we may close open tables here. If a MERGE table is affected, detach the children first. @@ -3374,9 +3378,10 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) that they cannot be moved into the unused_tables chain with these pointers set. */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - VOID(hash_delete(&open_cache,(uchar*) table)); + unlink_open_table(thd, table, 0); + /* Restart loop */ + prev= &thd->open_tables; + next= *prev; error=1; } else @@ -3411,7 +3416,7 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) } DBUG_PRINT("tcache", ("open tables to lock: %u", (uint) (tables_ptr - tables))); - if (tables != tables_ptr) // Should we get back old locks + if (tables != tables_ptr) // Should we get back old locks { MYSQL_LOCK *lock; /* @@ -3560,7 +3565,7 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock) char *key= table->s->table_cache_key.str; uint key_length= table->s->table_cache_key.length; - DBUG_PRINT("loop", ("table_name: %s", table->alias)); + DBUG_PRINT("loop", ("table_name: %s", table->alias ? table->alias : "")); HASH_SEARCH_STATE state; for (TABLE *search= (TABLE*) hash_first(&open_cache, (uchar*) key, key_length, &state); @@ -3898,6 +3903,7 @@ static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, int error; TABLE_SHARE *share; uint discover_retry_count= 0; + bool locked_table; DBUG_ENTER("open_unireg_entry"); safe_mutex_assert_owner(&LOCK_open); @@ -4018,8 +4024,10 @@ retry: } if (!entry->s || !entry->s->crashed) goto err; - // Code below is for repairing a crashed file - if ((error= lock_table_name(thd, table_list, TRUE))) + + // Code below is for repairing a crashed file + locked_table= table_list->table != 0; + if (! locked_table && (error= lock_table_name(thd, table_list, TRUE))) { if (error < 0) goto err; @@ -4053,12 +4061,13 @@ retry: else thd->clear_error(); // Clear error message pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); + if (!locked_table) + unlock_table_name(thd, table_list); if (error) goto err; break; - } + } if (Table_triggers_list::check_n_load(thd, share->db.str, share->table_name.str, entry, 0)) @@ -4286,7 +4295,6 @@ void detach_merge_children(TABLE *table, bool clear_refs) { TABLE_LIST *child_l; TABLE *parent= table->child_l ? table : table->parent; - bool first_detach; DBUG_ENTER("detach_merge_children"); /* Either table->child_l or table->parent must be set. Parent must have @@ -4304,7 +4312,7 @@ void detach_merge_children(TABLE *table, bool clear_refs) children attached yet. Also this is called for every child and the parent from close_thread_tables(). */ - if ((first_detach= parent->children_attached)) + if (parent->children_attached) { VOID(parent->file->extra(HA_EXTRA_DETACH_CHILDREN)); parent->children_attached= FALSE; @@ -4316,38 +4324,50 @@ void detach_merge_children(TABLE *table, bool clear_refs) if (clear_refs) { - /* In any case clear the own parent reference. (***) */ - table->parent= NULL; + if (table->parent) + { + /* In any case clear the own parent reference. (***) */ + table->parent= NULL; + table->file->extra(HA_EXTRA_DETACH_CHILD); + } /* - On the first detach, clear all references. If this table is the - parent, we still may need to clear the child references. The first - detach might not have done this. + Clear all references. If this table is the parent, we still may + need to clear the child references. The first detach might not + have done this. */ - if (first_detach || (table == parent)) + for (child_l= parent->child_l; ; child_l= child_l->next_global) { - /* Clear TABLE references to force new assignment at next open. */ - for (child_l= parent->child_l; ; child_l= child_l->next_global) + /* + Do not DBUG_ASSERT(child_l->table); open_tables might be + incomplete or we may have been called twice. + + Clear the parent reference of the children only on the first + detach. The children might already be closed. They will clear + it themselves when this function is called for them with + 'clear_refs' true. See above "(***)". + */ + if (child_l->table) { + if (child_l->table->parent) + { + child_l->table->parent= NULL; + if (child_l->table->db_stat) + child_l->table->file->extra(HA_EXTRA_DETACH_CHILD); + } /* - Do not DBUG_ASSERT(child_l->table); open_tables might be - incomplete. - - Clear the parent reference of the children only on the first - detach. The children might already be closed. They will clear - it themseves when this function is called for them with - 'clear_refs' true. See above "(***)". + Set alias to "" to ensure that table is not used if we are in + LOCK TABLES */ - if (first_detach && child_l->table) - child_l->table->parent= NULL; + ((char*) child_l->table->alias)[0]= 0; /* Clear the table reference to force new assignment at next open. */ child_l->table= NULL; - - /* Break when this was the last child. */ - if (&child_l->next_global == parent->child_last_l) - break; } + + /* Break when this was the last child. */ + if (&child_l->next_global == parent->child_last_l) + break; } } @@ -5144,9 +5164,11 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags) static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table) { + DBUG_ENTER("mark_real_tables_as_free_for_reuse"); for (; table; table= table->next_global) if (!table->placeholder()) table->table->query_id= 0; + DBUG_VOID_RETURN; } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index cd408ac723f..a1f8dbab175 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -328,7 +328,7 @@ const char *set_thd_proc_info(THD *thd, const char *info, const char *old_info= thd->proc_info; DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, - (info != NULL) ? info : "(null)")); + (info != NULL) ? info : "")); #if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) thd->profiling.status_change(info, calling_function, calling_file, calling_line); #endif diff --git a/sql/sql_class.h b/sql/sql_class.h index b3d78c4c801..7e044f63d0f 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1313,6 +1313,13 @@ public: return m_total_warn_count; } + /* Used to count any warnings pushed after calling set_ok_status(). */ + void increment_warning() + { + if (m_status != DA_EMPTY) + m_total_warn_count++; + } + Diagnostics_area() { reset_diagnostics_area(); } private: diff --git a/sql/sql_error.cc b/sql/sql_error.cc index 9ea7facbe41..835e60cd6ba 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -159,6 +159,8 @@ MYSQL_ERROR *push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, } thd->warn_count[(uint) level]++; thd->total_warn_count++; + /* Make sure we also count warnings pushed after calling set_ok_status(). */ + thd->main_da.increment_warning(); DBUG_RETURN(err); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 94164a30311..90ebfb8a223 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8014,6 +8014,8 @@ bool parse_sql(THD *thd, Object_creation_ctx *creation_ctx) { bool mysql_parse_status; + DBUG_ENTER("parse_sql"); + DBUG_ASSERT(thd->m_parser_state == NULL); /* Backup creation context. */ @@ -8047,7 +8049,7 @@ bool parse_sql(THD *thd, /* That's it. */ - return mysql_parse_status || thd->is_fatal_error; + DBUG_RETURN(mysql_parse_status || thd->is_fatal_error); } /** diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index e8a382ca8f6..f6864add19f 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -498,9 +498,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) thd->in_lock_tables= 1; if (reopen_tables(thd, 1, 1)) { - /* To be safe remove this table from the set of LOCKED TABLES */ - unlink_open_table(thd, tables->table, FALSE); - /* Ignore reopen_tables errors for now. It's better not leave master/slave in a inconsistent state. diff --git a/storage/maria/ma_bitmap.c b/storage/maria/ma_bitmap.c index d8b7a9b9a99..c0763b0612d 100644 --- a/storage/maria/ma_bitmap.c +++ b/storage/maria/ma_bitmap.c @@ -227,7 +227,8 @@ my_bool _ma_bitmap_init(MARIA_SHARE *share, File file) 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; + bitmap->flush_all_requested= 0; + bitmap->non_flushable= 0; /* Update size for bits */ /* TODO; Make this dependent of the row size */ @@ -311,8 +312,8 @@ my_bool _ma_bitmap_flush(MARIA_SHARE *share) pthread_mutex_lock(&share->bitmap.bitmap_lock); if (share->bitmap.changed) { - share->bitmap.changed= 0; res= write_changed_bitmap(share, &share->bitmap); + share->bitmap.changed= 0; } pthread_mutex_unlock(&share->bitmap.bitmap_lock); } @@ -355,7 +356,7 @@ my_bool _ma_bitmap_flush_all(MARIA_SHARE *share) pthread_mutex_lock(&bitmap->bitmap_lock); if (bitmap->changed || bitmap->changed_not_flushed) { - bitmap->flush_all_requested= TRUE; + bitmap->flush_all_requested++; #ifndef WRONG_BITMAP_FLUSH while (bitmap->non_flushable > 0) { @@ -363,6 +364,7 @@ my_bool _ma_bitmap_flush_all(MARIA_SHARE *share) pthread_cond_wait(&bitmap->bitmap_cond, &bitmap->bitmap_lock); } #endif + DBUG_ASSERT(bitmap->flush_all_requested == 1); /* Bitmap is in a flushable state: its contents in memory are reflected by log records (complete REDO-UNDO groups) and all bitmap pages are @@ -391,7 +393,7 @@ my_bool _ma_bitmap_flush_all(MARIA_SHARE *share) PCFLUSH_PINNED_AND_ERROR) res= TRUE; bitmap->changed_not_flushed= FALSE; - bitmap->flush_all_requested= FALSE; + bitmap->flush_all_requested--; /* Some well-behaved threads may be waiting for flush_all_requested to become false, wake them up. @@ -405,6 +407,70 @@ my_bool _ma_bitmap_flush_all(MARIA_SHARE *share) /** + @brief Lock bitmap from being used by another thread + + @fn _ma_bitmap_lock() + @param share Table's share + + @notes + This is a temporary solution for allowing someone to delete an inserted + duplicate-key row while someone else is doing concurrent inserts. + This is ok for now as duplicate key errors are not that common. + + In the future we will add locks for row-pages to ensure two threads doesn't + work at the same time on the same page. +*/ + +void _ma_bitmap_lock(MARIA_SHARE *share) +{ + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + DBUG_ENTER("_ma_bitmap_lock"); + + if (!share->now_transactional) + DBUG_VOID_RETURN; + + pthread_mutex_lock(&bitmap->bitmap_lock); + bitmap->flush_all_requested++; + while (bitmap->non_flushable) + { + DBUG_PRINT("info", ("waiting for bitmap to be flushable")); + pthread_cond_wait(&bitmap->bitmap_cond, &bitmap->bitmap_lock); + } + /* + Ensure that _ma_bitmap_flush_all() and _ma_bitmap_lock() are blocked. + ma_bitmap_flushable() is blocked thanks to 'flush_all_requested'. + */ + bitmap->non_flushable= 1; + pthread_mutex_unlock(&bitmap->bitmap_lock); + DBUG_VOID_RETURN; +} + +/** + @brief Unlock bitmap after _ma_bitmap_lock() + + @fn _ma_bitmap_unlock() + @param share Table's share +*/ + +void _ma_bitmap_unlock(MARIA_SHARE *share) +{ + MARIA_FILE_BITMAP *bitmap= &share->bitmap; + DBUG_ENTER("_ma_bitmap_unlock"); + + if (!share->now_transactional) + DBUG_VOID_RETURN; + DBUG_ASSERT(bitmap->flush_all_requested > 0 && bitmap->non_flushable == 1); + + pthread_mutex_lock(&bitmap->bitmap_lock); + bitmap->flush_all_requested--; + bitmap->non_flushable= 0; + pthread_mutex_unlock(&bitmap->bitmap_lock); + pthread_cond_broadcast(&bitmap->bitmap_cond); + DBUG_VOID_RETURN; +} + + +/** @brief Unpin all pinned bitmap pages @param share Table's share @@ -633,11 +699,12 @@ static void _ma_print_bitmap_changes(MARIA_FILE_BITMAP *bitmap) { uchar *pos, *end, *org_pos; ulong page; + DBUG_ENTER("_ma_print_bitmap_changes"); end= bitmap->map + bitmap->used_size; DBUG_LOCK_FILE; - fprintf(DBUG_FILE,"\nBitmap page changes at page %lu\n", - (ulong) bitmap->page); + fprintf(DBUG_FILE,"\nBitmap page changes at page: %lu bitmap: 0x%lx\n", + (ulong) bitmap->page, (long) bitmap->map); page= (ulong) bitmap->page+1; for (pos= bitmap->map, org_pos= bitmap->map + bitmap->block_size ; @@ -666,6 +733,7 @@ static void _ma_print_bitmap_changes(MARIA_FILE_BITMAP *bitmap) fputc('\n', DBUG_FILE); DBUG_UNLOCK_FILE; memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size); + DBUG_VOID_RETURN; } @@ -877,6 +945,7 @@ static void fill_block(MARIA_FILE_BITMAP *bitmap, { uint page, offset, tmp; uchar *data; + DBUG_ENTER("fill_block"); /* For each 6 bytes we have 6*8/3= 16 patterns */ page= ((uint) (best_data - bitmap->map)) / 6 * 16 + best_pos; @@ -902,6 +971,7 @@ static void fill_block(MARIA_FILE_BITMAP *bitmap, int2store(data, tmp); bitmap->changed= 1; DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); + DBUG_VOID_RETURN; } @@ -1016,7 +1086,11 @@ static my_bool allocate_tail(MARIA_FILE_BITMAP *bitmap, uint size, DBUG_PRINT("enter", ("size: %u", size)); LINT_INIT(best_pos); - DBUG_ASSERT(size <= MAX_TAIL_SIZE(bitmap->block_size)); + /* + We have to add DIR_ENTRY_SIZE here as this is not part of the data size + See call to allocate_tail() in find_tail(). + */ + DBUG_ASSERT(size <= MAX_TAIL_SIZE(bitmap->block_size) + DIR_ENTRY_SIZE); for (; data < end; data += 6) { @@ -1510,6 +1584,8 @@ static void use_head(MARIA_HA *info, pgcache_page_no_t page, uint size, MARIA_BITMAP_BLOCK *block; uchar *data; uint offset, tmp, offset_page; + DBUG_ENTER("use_head"); + DBUG_ASSERT(page % bitmap->pages_covered); block= dynamic_element(&info->bitmap_blocks, block_position, @@ -1532,6 +1608,7 @@ static void use_head(MARIA_HA *info, pgcache_page_no_t page, uint size, int2store(data, tmp); bitmap->changed= 1; DBUG_EXECUTE("bitmap", _ma_print_bitmap_changes(bitmap);); + DBUG_VOID_RETURN; } diff --git a/storage/maria/ma_blockrec.c b/storage/maria/ma_blockrec.c index 92ec916a9d1..fd02e2ac0ec 100644 --- a/storage/maria/ma_blockrec.c +++ b/storage/maria/ma_blockrec.c @@ -1993,19 +1993,6 @@ static my_bool write_tail(MARIA_HA *info, /* Keep BLOCKUSED_USE_ORG_BITMAP */ 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 */ @@ -2021,17 +2008,33 @@ static my_bool write_tail(MARIA_HA *info, 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))) + else { - page_link.unlock= PAGECACHE_LOCK_READ_UNLOCK; - page_link.changed= 1; - push_dynamic(&info->pinned_pages, (void*) &page_link); + 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); + } + + /* 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); + } } DBUG_RETURN(res); } @@ -2068,7 +2071,7 @@ static my_bool write_full_pages(MARIA_HA *info, uint data_size= FULL_PAGE_SIZE(block_size); uchar *buff= info->keyread_buff; uint page_count, sub_blocks; - my_off_t position; + my_off_t position, max_position; DBUG_ENTER("write_full_pages"); DBUG_PRINT("enter", ("length: %lu page: %lu page_count: %lu", (ulong) length, (ulong) block->page, @@ -2080,9 +2083,7 @@ static my_bool write_full_pages(MARIA_HA *info, 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); + max_position= (my_off_t) (page + page_count) * block_size; /* Increase data file size, if extended */ @@ -2105,8 +2106,7 @@ static my_bool write_full_pages(MARIA_HA *info, (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); + set_if_bigger(max_position, position); } lsn_store(buff, lsn); buff[PAGE_TYPE_OFFSET]= (uchar) BLOB_PAGE; @@ -2134,6 +2134,8 @@ static my_bool write_full_pages(MARIA_HA *info, page++; DBUG_ASSERT(block->used & BLOCKUSED_USED); } + if (share->state.state.data_file_length < max_position) + _ma_set_share_data_file_length(share, max_position); DBUG_RETURN(0); } @@ -2823,6 +2825,10 @@ static my_bool write_block_record(MARIA_HA *info, data+= diff_length; head_length= share->base.min_block_length; } + /* + If this is a redo entry (ie, undo_lsn != LSN_ERROR) then we should have + written exactly head_length bytes (same as original record). + */ DBUG_ASSERT(undo_lsn == LSN_ERROR || head_length == row_pos->length); int2store(row_pos->dir + 2, head_length); /* update empty space at start of block */ @@ -3117,11 +3123,6 @@ static my_bool write_block_record(MARIA_HA *info, } #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; @@ -3150,6 +3151,11 @@ static my_bool write_block_record(MARIA_HA *info, page_link.unlock= PAGECACHE_LOCK_READ_UNLOCK; page_link.changed= 1; push_dynamic(&info->pinned_pages, (void*) &page_link); + + /* 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 (share->now_transactional && (tmp_data_used || blob_full_pages_exists)) @@ -3588,7 +3594,7 @@ my_bool _ma_write_abort_block_record(MARIA_HA *info) MARIA_SHARE *share= info->s; DBUG_ENTER("_ma_write_abort_block_record"); - _ma_bitmap_flushable(info, 1); + _ma_bitmap_lock(share); /* Lock bitmap from other insert threads */ if (delete_head_or_tail(info, ma_recordpos_to_page(info->cur_row.lastpos), ma_recordpos_to_dir_entry(info->cur_row.lastpos), 1, @@ -3626,7 +3632,7 @@ my_bool _ma_write_abort_block_record(MARIA_HA *info) &lsn, (void*) 0)) res= 1; } - _ma_bitmap_flushable(info, -1); + _ma_bitmap_unlock(share); _ma_unpin_all_pages_and_finalize_row(info, lsn); DBUG_RETURN(res); } @@ -5158,6 +5164,7 @@ my_bool _ma_scan_init_block_record(MARIA_HA *info) 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; + info->scan.max_page= share->state.state.data_file_length / share->block_size; /* 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 @@ -5359,6 +5366,11 @@ restart_bitmap_scan: 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 (page >= info->scan.max_page) + { + DBUG_PRINT("info", ("Found end of file")); + DBUG_RETURN((my_errno= HA_ERR_END_OF_FILE)); + } if (!(pagecache_read(share->pagecache, &info->dfile, page, 0, info->scan.page_buff, @@ -7161,6 +7173,7 @@ my_bool _ma_apply_undo_row_update(MARIA_HA *info, LSN undo_lsn, header+= HA_CHECKSUM_STORE_SIZE; } length_on_head_page= uint2korr(header); + set_if_bigger(length_on_head_page, share->base.min_block_length); header+= 2; extent_count= pagerange_korr(header); header+= PAGERANGE_STORE_SIZE; diff --git a/storage/maria/ma_blockrec.h b/storage/maria/ma_blockrec.h index c39b0af73ad..a5858880dd0 100644 --- a/storage/maria/ma_blockrec.h +++ b/storage/maria/ma_blockrec.h @@ -217,6 +217,8 @@ uint _ma_bitmap_get_page_bits(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap, 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_lock(MARIA_SHARE *share); +void _ma_bitmap_unlock(MARIA_SHARE *share); void _ma_bitmap_set_pagecache_callbacks(PAGECACHE_FILE *file, MARIA_SHARE *share); #ifndef DBUG_OFF @@ -281,7 +283,8 @@ 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_get_status_no_versioning(void *param, my_bool concurrent_ins); +my_bool _ma_block_start_trans(void* param); +my_bool _ma_block_start_trans_no_versioning(void *param); 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_dbug.c b/storage/maria/ma_dbug.c index 0d2db0d4f4b..af90a108e2a 100644 --- a/storage/maria/ma_dbug.c +++ b/storage/maria/ma_dbug.c @@ -137,6 +137,7 @@ void _ma_print_keydata(FILE *stream, register HA_KEYSEG *keyseg, key=end; break; } +#endif case HA_KEYTYPE_BIT: { uint i; @@ -146,8 +147,6 @@ void _ma_print_keydata(FILE *stream, register HA_KEYSEG *keyseg, 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 */ diff --git a/storage/maria/ma_locking.c b/storage/maria/ma_locking.c index b355d7bc792..6bb308e5959 100644 --- a/storage/maria/ma_locking.c +++ b/storage/maria/ma_locking.c @@ -63,7 +63,7 @@ int maria_lock_database(MARIA_HA *info, int lock_type) { count= --share->w_locks; if (share->lock.update_status) - (*share->lock.update_status)(info); + _ma_update_status_with_lock(info); } --share->tot_locks; if (info->lock_type == F_WRLCK && !share->w_locks) diff --git a/storage/maria/ma_open.c b/storage/maria/ma_open.c index 30d099d939a..63e1801a39a 100644 --- a/storage/maria/ma_open.c +++ b/storage/maria/ma_open.c @@ -874,8 +874,8 @@ MARIA_HA *maria_open(const char *name, int mode, uint open_flags) 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; + share->lock.start_trans= _ma_block_start_trans; /* We can for the moment only allow multiple concurrent inserts only if there is no auto-increment key. To lift this restriction @@ -903,7 +903,7 @@ MARIA_HA *maria_open(const char *name, int mode, uint open_flags) else if (share->now_transactional) { DBUG_ASSERT(share->data_file_type == BLOCK_RECORD); - share->lock.get_status= _ma_block_get_status_no_versioning; + share->lock.start_trans= _ma_block_start_trans_no_versioning; } } #endif diff --git a/storage/maria/ma_state.c b/storage/maria/ma_state.c index c7eee7d511d..ca94d58264b 100644 --- a/storage/maria/ma_state.c +++ b/storage/maria/ma_state.c @@ -335,6 +335,25 @@ void _ma_update_status(void* param) } +/* + Same as ma_update_status() but take a lock in the table lock, to protect + against someone calling ma_get_status() from thr_lock() at the same time. +*/ + +void _ma_update_status_with_lock(MARIA_HA *info) +{ + my_bool locked= 0; + if (info->state == &info->state_save) + { + locked= 1; + pthread_mutex_lock(&info->s->lock.mutex); + } + (*info->s->lock.update_status)(info); + if (locked) + pthread_mutex_unlock(&info->s->lock.mutex); +} + + void _ma_restore_status(void *param) { MARIA_HA *info= (MARIA_HA*) param; @@ -585,7 +604,13 @@ void _ma_block_get_status(void* param, my_bool concurrent_insert) { DBUG_ASSERT(info->lock.type != TL_WRITE_CONCURRENT_INSERT); } + DBUG_VOID_RETURN; +} + +my_bool _ma_block_start_trans(void* param) +{ + MARIA_HA *info=(MARIA_HA*) param; if (info->s->lock_key_trees) { /* @@ -593,24 +618,22 @@ void _ma_block_get_status(void* param, my_bool concurrent_insert) out of memory conditions) TODO: Fix this by having one extra state pre-allocated */ - (void) _ma_setup_live_state(info); + return _ma_setup_live_state(info); } - else + + /* + Info->trn is set if this table is already handled and we are + called from maria_versioning() + */ + if (info->s->base.born_transactional && !info->trn) { /* - Info->trn is set if this table is already handled and we are - called from maria_versioning() + Assume for now that this doesn't fail (It can only fail in + out of memory conditions) */ - if (info->s->base.born_transactional && !info->trn) - { - /* - Assume for now that this doesn't fail (It can only fail in - out of memory conditions) - */ - (void) maria_create_trn_hook(info); - } + return maria_create_trn_hook(info) != 0; } - DBUG_VOID_RETURN; + return 0; } @@ -639,13 +662,10 @@ my_bool _ma_block_check_status(void *param __attribute__((unused))) /* Get status when transactional but not versioned */ -void _ma_block_get_status_no_versioning(void* param, - my_bool concurrent_insert - __attribute__((unused))) +my_bool _ma_block_start_trans_no_versioning(void* param) { MARIA_HA *info=(MARIA_HA*) param; DBUG_ENTER("_ma_block_get_status_no_version"); - DBUG_PRINT("enter", ("concurrent_insert %d", concurrent_insert)); DBUG_ASSERT(info->s->base.born_transactional); info->state->changed= 0; /* from _ma_reset_update_flag() */ @@ -655,9 +675,9 @@ void _ma_block_get_status_no_versioning(void* param, Assume for now that this doesn't fail (It can only fail in out of memory conditions) */ - (void) maria_create_trn_hook(info); + DBUG_RETURN(maria_create_trn_hook(info)); } - DBUG_VOID_RETURN; + DBUG_RETURN(0); } diff --git a/storage/maria/ma_state.h b/storage/maria/ma_state.h index f3317f0084a..03ce5c2ea8c 100644 --- a/storage/maria/ma_state.h +++ b/storage/maria/ma_state.h @@ -62,6 +62,7 @@ MARIA_STATE_HISTORY *_ma_remove_not_visible_states(MARIA_STATE_HISTORY 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_update_status_with_lock(MARIA_HA *info); 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); diff --git a/storage/maria/ma_write.c b/storage/maria/ma_write.c index 7ea73d17b9b..02eeec754ee 100644 --- a/storage/maria/ma_write.c +++ b/storage/maria/ma_write.c @@ -678,7 +678,6 @@ static int w_search(register MARIA_HA *info, uint32 comp_flag, MARIA_KEY *key, } else /* not HA_FULLTEXT, normal HA_NOSAME key */ { - DBUG_PRINT("warning", ("Duplicate key")); /* TODO When the index will support true versioning - with multiple @@ -696,6 +695,12 @@ static int w_search(register MARIA_HA *info, uint32 comp_flag, MARIA_KEY *key, info->dup_key_trid= _ma_trid_from_key(&tmp_key); info->dup_key_pos= dup_key_pos; my_errno= HA_ERR_FOUND_DUPP_KEY; + DBUG_PRINT("warning", + ("Duplicate key. dup_key_trid: %lu pos %lu visible: %d", + (ulong) info->dup_key_trid, + (ulong) info->dup_key_pos, + info->trn ? trnman_can_read_from(info->trn, + info->dup_key_trid) : 2)); goto err; } } diff --git a/storage/maria/maria_def.h b/storage/maria/maria_def.h index e58b149a051..ba97684b1aa 100644 --- a/storage/maria/maria_def.h +++ b/storage/maria/maria_def.h @@ -246,7 +246,7 @@ typedef struct st_maria_file_bitmap uint used_size; /* Size of bitmap head that is not 0 */ my_bool changed; /* 1 if page needs to be written */ my_bool changed_not_flushed; /* 1 if some bitmap is not flushed */ - my_bool flush_all_requested; /**< If _ma_bitmap_flush_all waiting */ + uint 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 */ @@ -476,7 +476,7 @@ 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; + pgcache_page_no_t bitmap_page, max_page; ulonglong bits; uint number_of_rows, bit_pos; MARIA_RECORD_POS row_base_page; diff --git a/storage/myisam/mi_check.c b/storage/myisam/mi_check.c index 6df148857c5..d24dba3d6b3 100644 --- a/storage/myisam/mi_check.c +++ b/storage/myisam/mi_check.c @@ -1338,7 +1338,7 @@ int chk_data_link(HA_CHECK *param, MI_INFO *info, my_bool extend) if (splits != info->s->state.split) { mi_check_print_warning(param, - "Found %10s key parts. Should be: %s", + "Found %10s parts. Should be: %s", llstr(splits,llbuff), llstr(info->s->state.split,llbuff2)); } diff --git a/storage/myisam/mi_dbug.c b/storage/myisam/mi_dbug.c index 1a61d4823b5..ab85ece07ab 100644 --- a/storage/myisam/mi_dbug.c +++ b/storage/myisam/mi_dbug.c @@ -131,6 +131,7 @@ void _mi_print_key(FILE *stream, register HA_KEYSEG *keyseg, key=end; break; } +#endif case HA_KEYTYPE_BIT: { uint i; @@ -140,8 +141,6 @@ void _mi_print_key(FILE *stream, register HA_KEYSEG *keyseg, 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 */ diff --git a/storage/myisam/mi_extra.c b/storage/myisam/mi_extra.c index deeb5d87cc1..648e5fb0024 100644 --- a/storage/myisam/mi_extra.c +++ b/storage/myisam/mi_extra.c @@ -392,6 +392,11 @@ int mi_extra(MI_INFO *info, enum ha_extra_function function, void *extra_arg) share->is_log_table= TRUE; pthread_mutex_unlock(&share->intern_lock); break; + case HA_EXTRA_DETACH_CHILD: /* When used with MERGE tables */ + info->open_flag&= ~HA_OPEN_MERGE_TABLE; + info->lock.priority&= ~THR_LOCK_MERGE_PRIV; + break; + case HA_EXTRA_KEY_CACHE: case HA_EXTRA_NO_KEY_CACHE: default: diff --git a/storage/myisam/mi_locking.c b/storage/myisam/mi_locking.c index 741b9b01986..97011831af8 100644 --- a/storage/myisam/mi_locking.c +++ b/storage/myisam/mi_locking.c @@ -22,6 +22,8 @@ #include "ftdefs.h" +static void mi_update_status_with_lock(MI_INFO *info); + /* lock table by F_UNLCK, F_RDLCK or F_WRLCK */ int mi_lock_database(MI_INFO *info, int lock_type) @@ -62,7 +64,7 @@ int mi_lock_database(MI_INFO *info, int lock_type) else { count= --share->w_locks; - mi_update_status(info); + mi_update_status_with_lock(info); } --share->tot_locks; if (info->lock_type == F_WRLCK && !share->w_locks && @@ -246,7 +248,7 @@ int mi_lock_database(MI_INFO *info, int lock_type) 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 < 0 ) + if ((info->open_flag & HA_OPEN_MERGE_TABLE) && (info->s)->kfile < 0) { error = HA_ERR_NO_SUCH_TABLE; } @@ -275,9 +277,11 @@ void mi_get_status(void* param, my_bool concurrent_insert) { MI_INFO *info=(MI_INFO*) param; DBUG_ENTER("mi_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, + DBUG_PRINT("info",("name: %s key_file: %lu data_file: %lu rows: %lu concurrent_insert: %d", + info->s->index_file_name, + (ulong) info->s->state.state.key_file_length, + (ulong) info->s->state.state.data_file_length, + (ulong) info->s->state.state.records, concurrent_insert)); #ifndef DBUG_OFF if (info->state->key_file_length > info->s->state.state.key_file_length || @@ -308,9 +312,11 @@ void mi_update_status(void* param) if (info->state == &info->save_state) { #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)); + DBUG_PRINT("info", + ("updating status: key_file: %lu data_file: %lu rows: %lu", + (ulong) info->state->key_file_length, + (ulong) info->state->data_file_length, + (ulong) info->state->records)); 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", @@ -344,6 +350,24 @@ void mi_update_status(void* param) DBUG_VOID_RETURN; } +/* + Same as mi_update_status() but take a lock in the table lock, to protect + against someone calling mi_get_status() from thr_lock() at the same time. +*/ + +static void mi_update_status_with_lock(MI_INFO *info) +{ + my_bool locked= 0; + if (info->state == &info->save_state) + { + locked= 1; + pthread_mutex_lock(&info->s->lock.mutex); + } + mi_update_status(info); + if (locked) + pthread_mutex_unlock(&info->s->lock.mutex); +} + void mi_restore_status(void *param) { @@ -409,6 +433,32 @@ my_bool mi_check_status(void *param) } +/** + Fix status for thr_lock_merge() + + @param org_table + @param new_table that should point on org_lock. new_table is 0 + in case this is the first occurence of the table in the lock + structure. +*/ + +void mi_fix_status(MI_INFO *org_table, MI_INFO *new_table) +{ + DBUG_ENTER("mi_fix_status"); + if (!new_table) + { + /* First in group. Set state as in mi_get_status() */ + org_table->state= &org_table->save_state; + } + else + { + /* Set new_table to use state from org_table (first lock of this table) */ + new_table->state= org_table->state; + } + DBUG_VOID_RETURN; +} + + /**************************************************************************** ** functions to read / write the state ****************************************************************************/ diff --git a/storage/myisam/mi_open.c b/storage/myisam/mi_open.c index e4240dec90c..9fc82846e91 100644 --- a/storage/myisam/mi_open.c +++ b/storage/myisam/mi_open.c @@ -119,7 +119,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) dflt_key_cache); DBUG_EXECUTE_IF("myisam_pretend_crashed_table_on_open", - if (strstr(name, "/t1")) + if (strstr(name, "/crashed")) { my_errno= HA_ERR_CRASHED; goto err; @@ -556,6 +556,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) share->lock.update_status=mi_update_status; share->lock.restore_status= mi_restore_status; share->lock.check_status=mi_check_status; + share->lock.fix_status= (void (*)(void *, void *)) mi_fix_status; } } #endif @@ -606,6 +607,7 @@ MI_INFO *mi_open(const char *name, int mode, uint open_flags) info.s=share; info.lastpos= HA_OFFSET_ERROR; info.update= (short) (HA_STATE_NEXT_FOUND+HA_STATE_PREV_FOUND); + info.open_flag= open_flags; info.opt_flag=READ_CHECK_USED; info.this_unique= (ulong) info.dfile; /* Uniq number in process */ if (share->data_file_type == COMPRESSED_RECORD) diff --git a/storage/myisam/myisamdef.h b/storage/myisam/myisamdef.h index 846be8f3b2e..20974e0b717 100644 --- a/storage/myisam/myisamdef.h +++ b/storage/myisam/myisamdef.h @@ -277,7 +277,9 @@ struct st_myisam_info */ ulong packed_length, blob_length; /* Length of found, packed record */ int dfile; /* The datafile */ + uint open_flag; /* Parameters for open */ uint opt_flag; /* Optim. for space/speed */ + uint once_flags; /* For MYISAMMRG */ uint update; /* If file changed since open */ int lastinx; /* Last used index */ uint lastkey_length; /* Length of key in lastkey */ @@ -303,12 +305,8 @@ struct st_myisam_info my_bool page_changed; /* If info->buff has to be reread for rnext */ my_bool buff_used; - my_bool once_flags; /* For MYISAMMRG */ index_cond_func_t index_cond_func; /* Index condition function */ void *index_cond_func_arg; /* parameter for the func */ -#ifdef __WIN__ - my_bool owned_by_merge; /* This MyISAM table is part of a merge union */ -#endif #ifdef THREAD THR_LOCK_DATA lock; #endif @@ -717,6 +715,7 @@ 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_fix_status(MI_INFO *org_table, MI_INFO *new_table); void mi_disable_non_unique_index(MI_INFO *info, ha_rows rows); extern MI_INFO *test_if_reopen(char *filename); diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index be3f0ca4813..c61d65572cb 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -86,8 +86,6 @@ On parent open the storage engine structures are allocated and initialized. They stay with the open table until its final close. - - */ #ifdef USE_PRAGMA_IMPLEMENTATION @@ -1070,6 +1068,8 @@ THR_LOCK_DATA **ha_myisammrg::store_lock(THD *thd, open_table != file->end_table ; open_table++) { + open_table->table->lock.priority|= THR_LOCK_MERGE_PRIV; + *(to++)= &open_table->table->lock; if (lock_type != TL_IGNORE && open_table->table->lock.type == TL_UNLOCK) open_table->table->lock.type=lock_type; diff --git a/storage/myisammrg/myrg_locking.c b/storage/myisammrg/myrg_locking.c index 4f1e3f844a1..a414cee7bb8 100644 --- a/storage/myisammrg/myrg_locking.c +++ b/storage/myisammrg/myrg_locking.c @@ -27,15 +27,8 @@ int myrg_lock_database(MYRG_INFO *info, int lock_type) error=0; for (file=info->open_tables ; file != info->end_table ; file++) { -#ifdef __WIN__ - /* - Make sure this table is marked as owned by a merge table. - The semaphore is never released as long as table remains - in memory. This should be refactored into a more generic - approach (observer pattern) - */ - (file->table)->owned_by_merge = TRUE; -#endif + DBUG_ASSERT(file->table->open_flag & HA_OPEN_MERGE_TABLE); + if ((new_error=mi_lock_database(file->table,lock_type))) { error=new_error; diff --git a/storage/myisammrg/myrg_open.c b/storage/myisammrg/myrg_open.c index 17c9b4ba4d1..ba452854808 100644 --- a/storage/myisammrg/myrg_open.c +++ b/storage/myisammrg/myrg_open.c @@ -27,8 +27,9 @@ if handle_locking is 0 then exit with error if some table is locked if handle_locking is 1 then wait if table is locked - NOTE: This function is not used in the MySQL server. It is for - MERGE use independent from MySQL. Currently there is some code + NOTE: This function is only used in the MySQL server when a + table is cloned. It is also used for usage of MERGE + independent from MySQL. Currently there is some code duplication between myrg_open() and myrg_parent_open() + myrg_attach_children(). Please duplicate changes in these functions or make common sub-functions. @@ -93,7 +94,8 @@ MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking) } else fn_format(buff, buff, "", "", 0); - if (!(isam=mi_open(buff,mode,(handle_locking?HA_OPEN_WAIT_IF_LOCKED:0)))) + if (!(isam=mi_open(buff,mode,(handle_locking?HA_OPEN_WAIT_IF_LOCKED:0) | + HA_OPEN_MERGE_TABLE))) { if (handle_locking & HA_OPEN_FOR_REPAIR) { @@ -430,6 +432,8 @@ int myrg_attach_children(MYRG_INFO *m_info, int handle_locking, m_info->open_tables[child_nr].table= myisam; m_info->open_tables[child_nr].file_offset= (my_off_t) file_offset; file_offset+= myisam->state->data_file_length; + /* Mark as MERGE table */ + myisam->open_flag|= HA_OPEN_MERGE_TABLE; /* Check table definition match. */ if (m_info->reclength != myisam->s->base.reclength) diff --git a/storage/xtradb/buf/buf0buf.c b/storage/xtradb/buf/buf0buf.c index 55ff207cf11..1c08bd6d0bf 100644 --- a/storage/xtradb/buf/buf0buf.c +++ b/storage/xtradb/buf/buf0buf.c @@ -1116,7 +1116,7 @@ init_again: if (shm_info->buf_pool_backup.LRU_old) shm_info->buf_pool_backup.LRU_old = (buf_page_t*)((byte*)(shm_info->buf_pool_backup.LRU_old) - + (((void*)shm_info->buf_pool_backup.LRU_old > previous_frame_address) + + (((byte*)shm_info->buf_pool_backup.LRU_old > previous_frame_address) ? logi_offset : blocks_offset)); UT_LIST_OFFSET(unzip_LRU, buf_block_t, shm_info->buf_pool_backup.unzip_LRU, diff --git a/storage/xtradb/buf/buf0lru.c b/storage/xtradb/buf/buf0lru.c index 79c7c0d3bbe..94828940fd4 100644 --- a/storage/xtradb/buf/buf0lru.c +++ b/storage/xtradb/buf/buf0lru.c @@ -2265,7 +2265,7 @@ buf_LRU_file_restore(void) ulint req = 0; ibool terminated = FALSE; ibool ret = FALSE; - dump_record_t* records; + dump_record_t* records= 0; ulint size; ulint size_high; ulint length; diff --git a/storage/xtradb/include/os0sync.h b/storage/xtradb/include/os0sync.h index 0c22162b900..c230a03b6db 100644 --- a/storage/xtradb/include/os0sync.h +++ b/storage/xtradb/include/os0sync.h @@ -189,7 +189,7 @@ os_event_wait_low( /**********************************************************//** Waits for an event object until it is in the signaled state or -a timeout is exceeded. In Unix the timeout is always infinite. +a timeout is exceeded. @return 0 if success, OS_SYNC_TIME_EXCEEDED if timeout was exceeded */ UNIV_INTERN ulint diff --git a/storage/xtradb/include/srv0srv.h b/storage/xtradb/include/srv0srv.h index dc455581350..8c64d5cee71 100644 --- a/storage/xtradb/include/srv0srv.h +++ b/storage/xtradb/include/srv0srv.h @@ -57,6 +57,9 @@ extern const char srv_mysql50_table_name_prefix[9]; thread starts running */ extern os_event_t srv_lock_timeout_thread_event; +/* This event is set to tell the purge thread to shut down */ +extern os_event_t srv_purge_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 \ diff --git a/storage/xtradb/log/log0log.c b/storage/xtradb/log/log0log.c index fade31037b5..b9f19aeff31 100644 --- a/storage/xtradb/log/log0log.c +++ b/storage/xtradb/log/log0log.c @@ -3102,6 +3102,7 @@ logs_empty_and_mark_files_at_shutdown(void) algorithm only works if the server is idle at shutdown */ srv_shutdown_state = SRV_SHUTDOWN_CLEANUP; + os_event_set(srv_purge_thread_event); loop: os_thread_sleep(100000); diff --git a/storage/xtradb/os/os0sync.c b/storage/xtradb/os/os0sync.c index 60467242e14..f9ab58c2ee4 100644 --- a/storage/xtradb/os/os0sync.c +++ b/storage/xtradb/os/os0sync.c @@ -31,6 +31,9 @@ Created 9/6/1995 Heikki Tuuri #ifdef __WIN__ #include <windows.h> +#else +#include <sys/time.h> +#include <time.h> #endif #include "ut0mem.h" @@ -407,14 +410,14 @@ os_event_wait_low( /**********************************************************//** Waits for an event object until it is in the signaled state or -a timeout is exceeded. In Unix the timeout is always infinite. +a timeout is exceeded. @return 0 if success, OS_SYNC_TIME_EXCEEDED if timeout was exceeded */ UNIV_INTERN ulint os_event_wait_time( /*===============*/ os_event_t event, /*!< in: event to wait */ - ulint time) /*!< in: timeout in microseconds, or + ulint wtime) /*!< in: timeout in microseconds, or OS_SYNC_INFINITE_TIME */ { #ifdef __WIN__ @@ -422,8 +425,8 @@ os_event_wait_time( ut_a(event); - if (time != OS_SYNC_INFINITE_TIME) { - err = WaitForSingleObject(event->handle, (DWORD) time / 1000); + if (wtime != OS_SYNC_INFINITE_TIME) { + err = WaitForSingleObject(event->handle, (DWORD) wtime / 1000); } else { err = WaitForSingleObject(event->handle, INFINITE); } @@ -439,13 +442,47 @@ os_event_wait_time( return(1000000); /* dummy value to eliminate compiler warn. */ } #else - UT_NOT_USED(time); + int err; + int ret = 0; + ulint tmp; + ib_int64_t old_count; + struct timeval tv_start; + struct timespec timeout; + + if (wtime == OS_SYNC_INFINITE_TIME) { + os_event_wait(event); + return 0; + } + + /* Compute the absolute point in time at which to time out. */ + gettimeofday(&tv_start, NULL); + tmp = tv_start.tv_usec + wtime; + timeout.tv_sec = tv_start.tv_sec + (tmp / 1000000); + timeout.tv_nsec = (tmp % 1000000) * 1000; + + os_fast_mutex_lock(&(event->os_mutex)); + old_count = event->signal_count; + + for (;;) { + if (event->is_set == TRUE || event->signal_count != old_count) + break; + + err = pthread_cond_timedwait(&(event->cond_var), + &(event->os_mutex), &timeout); + if (err == ETIMEDOUT) { + ret = OS_SYNC_TIME_EXCEEDED; + break; + } + } - /* In Posix this is just an ordinary, infinite wait */ + os_fast_mutex_unlock(&(event->os_mutex)); - os_event_wait(event); + if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { + + os_thread_exit(NULL); + } - return(0); + return ret; #endif } diff --git a/storage/xtradb/srv/srv0srv.c b/storage/xtradb/srv/srv0srv.c index c1d0f255c64..43799aab196 100644 --- a/storage/xtradb/srv/srv0srv.c +++ b/storage/xtradb/srv/srv0srv.c @@ -704,6 +704,8 @@ UNIV_INTERN srv_slot_t* srv_mysql_table = NULL; UNIV_INTERN os_event_t srv_lock_timeout_thread_event; +UNIV_INTERN os_event_t srv_purge_thread_event; + UNIV_INTERN srv_sys_t* srv_sys = NULL; /* padding to prevent other memory update hotspots from residing on @@ -1009,6 +1011,7 @@ srv_init(void) } srv_lock_timeout_thread_event = os_event_create(NULL); + srv_purge_thread_event = os_event_create(NULL); for (i = 0; i < SRV_MASTER + 1; i++) { srv_n_threads_active[i] = 0; @@ -3337,9 +3340,10 @@ loop: mutex_exit(&kernel_mutex); sleep_ms = 10; + os_event_reset(srv_purge_thread_event); } - os_thread_sleep( sleep_ms * 1000 ); + os_event_wait_time(srv_purge_thread_event, sleep_ms * 1000); history_len = trx_sys->rseg_history_len; if (history_len > 1000) |