Commit 7bafd119 authored by unknown's avatar unknown

Changes from the innodb-5.0-ss115 snapshot.

 Fixed bugs:
 BUG#15991: "innodb-file-per-table + symlink database + rename = crash"
 BUG#15650: "DELETE with LEFT JOIN crashes server"
 BUG#15308: "Problem of Order with Enum Column in Primary Key"
 BUG#14189: "VARBINARY and BINARY variables: trailing space ignored"


innobase/include/data0type.h:
  Changes from the innodb-5.0-ss115 snapshot.
innobase/include/data0type.ic:
  Changes from the innodb-5.0-ss115 snapshot.
   Fixed bug #14189. dtype_get_pad_char(): Do not pad VARBINARY
   or BINARY cloumns.
innobase/include/lock0lock.h:
  Changes from the innodb-5.0-ss115 snapshot.
innobase/include/os0file.h:
  Changes from the innodb-5.0-ss115 snapshot.
   os_file_handle_error(): Map the error codes EXDEV, ENOTDIR, and
   EISDIR to the new code OS_FILE_PATH_ERROR. Treat this code as
   OS_FILE_PATH_ERROR. This fixes the crash on RENAME TABLE when
   the .ibd file is a symbolic link to a different file system
   (bug#15991).
innobase/include/row0mysql.h:
  Changes from the innodb-5.0-ss115 snapshot.
innobase/lock/lock0lock.c:
  Changes from the innodb-5.0-ss115 snapshot.
   lock_rec_unlock(): Initialize local variable release_lock,
   in order to avoid dereferencing an uninitialized pointer
   when no lock exists on rec.
innobase/os/os0file.c:
  Changes from the innodb-5.0-ss115 snapshot.
   os_file_handle_error(): Map the error codes EXDEV, ENOTDIR, and
     EISDIR to the new code OS_FILE_PATH_ERROR. Treat this code as
     OS_FILE_PATH_ERROR. This fixes the crash on RENAME TABLE when
     the .ibd file is a symbolic link to a different file system
     (bug#15991).
   Protect the increment and decrement operations on the statistic
     variables os_n_pending_writes/reads with os_file_count_mutes.
innobase/row/row0ins.c:
  Changes from the innodb-5.0-ss115 snapshot.
   Fixed bug #14189. row_ins_cascade_calc_update_vec(): Refuse
   ON UPDATE_CASCADE when trying to change the length of of a
   VARBINARY column that refers to or is referenced by a BINARY
   column. BINARY columns are no longer padded on comparison,
   and thus they cannot be padded on storage either.
innobase/row/row0mysql.c:
  Changes from the innodb-5.0-ss115 snapshot.
   Fixed bug on unlock_row. In a unlock_row we may unlock
   only the latest lock granted to this transaction to the row.
innobase/row/row0sel.c:
  Changes from the innodb-5.0-ss115 snapshot.
   Fixed bug #15308.
   Fixed bug #14189: innobase_init(): Assert that
     DATA_MYSQL_BINARY_CHARSET_COLL == my_charset_bin.number.
   After review fixes for unlock bug where unlock released all
     locks transaction requested for a row. Only a latest requested
     lock to a row should be released. Update function comments to
     reflect current state. Persistent cursor should be stored
     whenever select lock type != LOCK_NONE.
innobase/trx/trx0trx.c:
  Changes from the innodb-5.0-ss115 snapshot.
   trx_commit_off_kernel(): Do not write empty trx->mysql_log_file_name.
mysql-test/r/innodb.result:
  Changes from the innodb-5.0-ss115 snapshot.
mysql-test/t/innodb.test:
  Changes from the innodb-5.0-ss115 snapshot.
sql/ha_innodb.cc:
  Changes from the innodb-5.0-ss115 snapshot.
   Fixed bug #15308.
   Fixed bug #14189: innobase_init(): Assert that
     DATA_MYSQL_BINARY_CHARSET_COLL == my_charset_bin.number.
   After review fixes for unlock bug where unlock released all
     locks transaction requested for a row. Only a latest requested
     lock to a row should be released. Update function comments to
     reflect current state. Persistent cursor should be stored
     whenever select lock type != LOCK_NONE.
mysql-test/r/innodb_unsafe_binlog.result:
  Changes from the innodb-5.0-ss115 snapshot.
mysql-test/t/innodb_unsafe_binlog-master.opt:
  Changes from the innodb-5.0-ss115 snapshot.
mysql-test/t/innodb_unsafe_binlog.test:
  Changes from the innodb-5.0-ss115 snapshot.
   Added testcases for bug #15650.
parent fe1970d4
...@@ -13,6 +13,7 @@ Created 1/16/1996 Heikki Tuuri ...@@ -13,6 +13,7 @@ Created 1/16/1996 Heikki Tuuri
extern ulint data_mysql_default_charset_coll; extern ulint data_mysql_default_charset_coll;
#define DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL 8 #define DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL 8
#define DATA_MYSQL_BINARY_CHARSET_COLL 63
/* SQL data type struct */ /* SQL data type struct */
typedef struct dtype_struct dtype_t; typedef struct dtype_struct dtype_t;
...@@ -311,7 +312,7 @@ dtype_get_pad_char( ...@@ -311,7 +312,7 @@ dtype_get_pad_char(
/*===============*/ /*===============*/
/* out: padding character code, or /* out: padding character code, or
ULINT_UNDEFINED if no padding specified */ ULINT_UNDEFINED if no padding specified */
dtype_t* type); /* in: type */ const dtype_t* type); /* in: type */
/*************************************************************************** /***************************************************************************
Returns the size of a fixed size data type, 0 if not a fixed size type. */ Returns the size of a fixed size data type, 0 if not a fixed size type. */
UNIV_INLINE UNIV_INLINE
......
...@@ -188,26 +188,35 @@ dtype_get_pad_char( ...@@ -188,26 +188,35 @@ dtype_get_pad_char(
/*===============*/ /*===============*/
/* out: padding character code, or /* out: padding character code, or
ULINT_UNDEFINED if no padding specified */ ULINT_UNDEFINED if no padding specified */
dtype_t* type) /* in: type */ const dtype_t* type) /* in: type */
{ {
if (type->mtype == DATA_CHAR switch (type->mtype) {
|| type->mtype == DATA_VARCHAR case DATA_FIXBINARY:
|| type->mtype == DATA_BINARY case DATA_BINARY:
|| type->mtype == DATA_FIXBINARY if (UNIV_UNLIKELY(dtype_get_charset_coll(type->prtype)
|| type->mtype == DATA_MYSQL == DATA_MYSQL_BINARY_CHARSET_COLL)) {
|| type->mtype == DATA_VARMYSQL /* Starting from 5.0.18, do not pad
|| (type->mtype == DATA_BLOB VARBINARY or BINARY columns. */
&& (type->prtype & DATA_BINARY_TYPE) == 0)) { 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 /* Space is the padding character for all char and binary
strings, and starting from 5.0.3, also for TEXT strings. */ strings, and starting from 5.0.3, also for TEXT strings. */
return((ulint)' '); return(0x20);
case DATA_BLOB:
if ((type->prtype & DATA_BINARY_TYPE) == 0) {
return(0x20);
}
/* Fall through */
default:
/* No padding specified */
return(ULINT_UNDEFINED);
} }
/* No padding specified */
return(ULINT_UNDEFINED);
} }
/************************************************************************** /**************************************************************************
......
...@@ -64,14 +64,6 @@ lock_clust_rec_some_has_impl( ...@@ -64,14 +64,6 @@ lock_clust_rec_some_has_impl(
dict_index_t* index, /* in: clustered index */ dict_index_t* index, /* in: clustered index */
const ulint* offsets);/* in: rec_get_offsets(rec, index) */ const ulint* offsets);/* in: rec_get_offsets(rec, index) */
/***************************************************************** /*****************************************************************
Resets the lock bits for a single record. Releases transactions
waiting for lock requests here. */
void
lock_rec_reset_and_release_wait(
/*============================*/
rec_t* rec); /* in: record whose locks bits should be reset */
/*****************************************************************
Makes a record to inherit the locks of another record as gap type Makes a record to inherit the locks of another record as gap type
locks, but does not reset the lock bits of the other record. Also locks, but does not reset the lock bits of the other record. Also
waiting lock requests on rec are inherited as GRANTED gap locks. */ waiting lock requests on rec are inherited as GRANTED gap locks. */
...@@ -427,6 +419,18 @@ lock_is_on_table( ...@@ -427,6 +419,18 @@ lock_is_on_table(
/*=============*/ /*=============*/
/* out: TRUE if there are lock(s) */ /* out: TRUE if there are lock(s) */
dict_table_t* table); /* in: database table in dictionary cache */ 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. */
void
lock_rec_unlock(
/*============*/
trx_t* trx, /* in: transaction that has set a record
lock */
rec_t* rec, /* in: record */
ulint lock_mode); /* in: LOCK_S or LOCK_X */
/************************************************************************* /*************************************************************************
Releases a table lock. Releases a table lock.
Releases possible other transactions waiting for this lock. */ Releases possible other transactions waiting for this lock. */
......
...@@ -91,9 +91,10 @@ log. */ ...@@ -91,9 +91,10 @@ log. */
#define OS_FILE_NOT_FOUND 71 #define OS_FILE_NOT_FOUND 71
#define OS_FILE_DISK_FULL 72 #define OS_FILE_DISK_FULL 72
#define OS_FILE_ALREADY_EXISTS 73 #define OS_FILE_ALREADY_EXISTS 73
#define OS_FILE_AIO_RESOURCES_RESERVED 74 /* wait for OS aio resources #define OS_FILE_PATH_ERROR 74
#define OS_FILE_AIO_RESOURCES_RESERVED 75 /* wait for OS aio resources
to become available again */ to become available again */
#define OS_FILE_ERROR_NOT_SPECIFIED 75 #define OS_FILE_ERROR_NOT_SPECIFIED 76
/* Types for aio operations */ /* Types for aio operations */
#define OS_FILE_READ 10 #define OS_FILE_READ 10
......
...@@ -250,8 +250,9 @@ trx_register_new_rec_lock() to store the information which new record locks ...@@ -250,8 +250,9 @@ 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, 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 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 in the case of an UPDATE or a DELETE statement, where the row lock is of the
LOCK_X type. LOCK_X or LOCK_S type.
Thus, this implements a 'mini-rollback' that releases the latest record
Thus, this implements a 'mini-rollback' that releases the latest record
locks we set. */ locks we set. */
int int
......
...@@ -2392,7 +2392,7 @@ lock_rec_free_all_from_discard_page( ...@@ -2392,7 +2392,7 @@ lock_rec_free_all_from_discard_page(
/***************************************************************** /*****************************************************************
Resets the lock bits for a single record. Releases transactions waiting for Resets the lock bits for a single record. Releases transactions waiting for
lock requests here. */ lock requests here. */
static
void void
lock_rec_reset_and_release_wait( lock_rec_reset_and_release_wait(
/*============================*/ /*============================*/
...@@ -3760,6 +3760,75 @@ lock_table_dequeue( ...@@ -3760,6 +3760,75 @@ lock_table_dequeue(
/*=========================== LOCK RELEASE ==============================*/ /*=========================== 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. */
void
lock_rec_unlock(
/*============*/
trx_t* trx, /* in: transaction that has set a record
lock */
rec_t* rec, /* in: record */
ulint lock_mode) /* in: LOCK_S or LOCK_X */
{
lock_t* lock;
lock_t* release_lock = NULL;
ulint heap_no;
ut_ad(trx && rec);
mutex_enter(&kernel_mutex);
heap_no = rec_get_heap_no(rec, page_rec_is_comp(rec));
lock = lock_rec_get_first(rec);
/* 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(rec, 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(rec);
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(rec, lock);
}
mutex_exit(&kernel_mutex);
}
/************************************************************************* /*************************************************************************
Releases a table lock. Releases a table lock.
Releases possible other transactions waiting for this lock. */ Releases possible other transactions waiting for this lock. */
......
...@@ -160,15 +160,12 @@ time_t os_last_printout; ...@@ -160,15 +160,12 @@ time_t os_last_printout;
ibool os_has_said_disk_full = FALSE; ibool os_has_said_disk_full = FALSE;
/* The mutex protecting the following counts of pending pread and pwrite /* The mutex protecting the following counts of pending I/O operations */
operations */
static os_mutex_t os_file_count_mutex; static os_mutex_t os_file_count_mutex;
ulint os_file_n_pending_preads = 0; ulint os_file_n_pending_preads = 0;
ulint os_file_n_pending_pwrites = 0; ulint os_file_n_pending_pwrites = 0;
ulint os_n_pending_writes = 0;
/* These are not protected by any mutex */ ulint os_n_pending_reads = 0;
ulint os_n_pending_writes = 0;
ulint os_n_pending_reads = 0;
/*************************************************************************** /***************************************************************************
Gets the operating system version. Currently works only on Windows. */ Gets the operating system version. Currently works only on Windows. */
...@@ -314,6 +311,8 @@ os_file_get_last_error( ...@@ -314,6 +311,8 @@ os_file_get_last_error(
return(OS_FILE_NOT_FOUND); return(OS_FILE_NOT_FOUND);
} else if (err == EEXIST) { } else if (err == EEXIST) {
return(OS_FILE_ALREADY_EXISTS); return(OS_FILE_ALREADY_EXISTS);
} else if (err == EXDEV || err == ENOTDIR || err == EISDIR) {
return(OS_FILE_PATH_ERROR);
} else { } else {
return(100 + err); return(100 + err);
} }
...@@ -363,7 +362,8 @@ os_file_handle_error( ...@@ -363,7 +362,8 @@ os_file_handle_error(
return(TRUE); return(TRUE);
} else if (err == OS_FILE_ALREADY_EXISTS) { } else if (err == OS_FILE_ALREADY_EXISTS
|| err == OS_FILE_PATH_ERROR) {
return(FALSE); return(FALSE);
} else { } else {
...@@ -467,7 +467,8 @@ os_file_handle_error_no_exit( ...@@ -467,7 +467,8 @@ os_file_handle_error_no_exit(
return(TRUE); return(TRUE);
} else if (err == OS_FILE_ALREADY_EXISTS) { } else if (err == OS_FILE_ALREADY_EXISTS
|| err == OS_FILE_PATH_ERROR) {
return(FALSE); return(FALSE);
} else { } else {
...@@ -1905,12 +1906,14 @@ os_file_pread( ...@@ -1905,12 +1906,14 @@ os_file_pread(
#if defined(HAVE_PREAD) && !defined(HAVE_BROKEN_PREAD) #if defined(HAVE_PREAD) && !defined(HAVE_BROKEN_PREAD)
os_mutex_enter(os_file_count_mutex); os_mutex_enter(os_file_count_mutex);
os_file_n_pending_preads++; os_file_n_pending_preads++;
os_n_pending_reads++;
os_mutex_exit(os_file_count_mutex); os_mutex_exit(os_file_count_mutex);
n_bytes = pread(file, buf, (ssize_t)n, offs); n_bytes = pread(file, buf, (ssize_t)n, offs);
os_mutex_enter(os_file_count_mutex); os_mutex_enter(os_file_count_mutex);
os_file_n_pending_preads--; os_file_n_pending_preads--;
os_n_pending_reads--;
os_mutex_exit(os_file_count_mutex); os_mutex_exit(os_file_count_mutex);
return(n_bytes); return(n_bytes);
...@@ -1920,6 +1923,10 @@ os_file_pread( ...@@ -1920,6 +1923,10 @@ os_file_pread(
ssize_t ret; ssize_t ret;
ulint i; 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 */ /* Protect the seek / read operation with a mutex */
i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES;
...@@ -1928,15 +1935,17 @@ os_file_pread( ...@@ -1928,15 +1935,17 @@ os_file_pread(
ret_offset = lseek(file, offs, SEEK_SET); ret_offset = lseek(file, offs, SEEK_SET);
if (ret_offset < 0) { if (ret_offset < 0) {
os_mutex_exit(os_file_seek_mutexes[i]); ret = -1;
} else {
return(-1); ret = read(file, buf, (ssize_t)n);
} }
ret = read(file, buf, (ssize_t)n);
os_mutex_exit(os_file_seek_mutexes[i]); 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); return(ret);
} }
#endif #endif
...@@ -1981,12 +1990,14 @@ os_file_pwrite( ...@@ -1981,12 +1990,14 @@ os_file_pwrite(
#if defined(HAVE_PWRITE) && !defined(HAVE_BROKEN_PREAD) #if defined(HAVE_PWRITE) && !defined(HAVE_BROKEN_PREAD)
os_mutex_enter(os_file_count_mutex); os_mutex_enter(os_file_count_mutex);
os_file_n_pending_pwrites++; os_file_n_pending_pwrites++;
os_n_pending_writes++;
os_mutex_exit(os_file_count_mutex); os_mutex_exit(os_file_count_mutex);
ret = pwrite(file, buf, (ssize_t)n, offs); ret = pwrite(file, buf, (ssize_t)n, offs);
os_mutex_enter(os_file_count_mutex); os_mutex_enter(os_file_count_mutex);
os_file_n_pending_pwrites--; os_file_n_pending_pwrites--;
os_n_pending_writes--;
os_mutex_exit(os_file_count_mutex); os_mutex_exit(os_file_count_mutex);
# ifdef UNIV_DO_FLUSH # ifdef UNIV_DO_FLUSH
...@@ -2008,6 +2019,10 @@ os_file_pwrite( ...@@ -2008,6 +2019,10 @@ os_file_pwrite(
off_t ret_offset; off_t ret_offset;
ulint i; 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 */ /* Protect the seek / write operation with a mutex */
i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES;
...@@ -2016,9 +2031,9 @@ os_file_pwrite( ...@@ -2016,9 +2031,9 @@ os_file_pwrite(
ret_offset = lseek(file, offs, SEEK_SET); ret_offset = lseek(file, offs, SEEK_SET);
if (ret_offset < 0) { if (ret_offset < 0) {
os_mutex_exit(os_file_seek_mutexes[i]); ret = -1;
return(-1); goto func_exit;
} }
ret = write(file, buf, (ssize_t)n); ret = write(file, buf, (ssize_t)n);
...@@ -2036,8 +2051,13 @@ os_file_pwrite( ...@@ -2036,8 +2051,13 @@ os_file_pwrite(
} }
# endif /* UNIV_DO_FLUSH */ # endif /* UNIV_DO_FLUSH */
func_exit:
os_mutex_exit(os_file_seek_mutexes[i]); 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); return(ret);
} }
#endif #endif
...@@ -2082,9 +2102,13 @@ try_again: ...@@ -2082,9 +2102,13 @@ try_again:
low = (DWORD) offset; low = (DWORD) offset;
high = (DWORD) offset_high; 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 */ /* Protect the seek / read operation with a mutex */
i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES;
os_mutex_enter(os_file_seek_mutexes[i]); os_mutex_enter(os_file_seek_mutexes[i]);
ret2 = SetFilePointer(file, low, &high, FILE_BEGIN); ret2 = SetFilePointer(file, low, &high, FILE_BEGIN);
...@@ -2093,17 +2117,21 @@ try_again: ...@@ -2093,17 +2117,21 @@ try_again:
os_mutex_exit(os_file_seek_mutexes[i]); 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; goto error_handling;
} }
os_n_pending_reads++;
ret = ReadFile(file, buf, (DWORD) n, &len, NULL); ret = ReadFile(file, buf, (DWORD) n, &len, NULL);
os_n_pending_reads--;
os_mutex_exit(os_file_seek_mutexes[i]); 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) { if (ret && len == n) {
return(TRUE); return(TRUE);
} }
...@@ -2114,12 +2142,8 @@ try_again: ...@@ -2114,12 +2142,8 @@ try_again:
os_bytes_read_since_printout += n; os_bytes_read_since_printout += n;
try_again: try_again:
os_n_pending_reads++;
ret = os_file_pread(file, buf, n, offset, offset_high); ret = os_file_pread(file, buf, n, offset, offset_high);
os_n_pending_reads--;
if ((ulint)ret == n) { if ((ulint)ret == n) {
return(TRUE); return(TRUE);
...@@ -2193,6 +2217,10 @@ try_again: ...@@ -2193,6 +2217,10 @@ try_again:
low = (DWORD) offset; low = (DWORD) offset;
high = (DWORD) offset_high; 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 */ /* Protect the seek / read operation with a mutex */
i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES;
...@@ -2204,17 +2232,21 @@ try_again: ...@@ -2204,17 +2232,21 @@ try_again:
os_mutex_exit(os_file_seek_mutexes[i]); 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; goto error_handling;
} }
os_n_pending_reads++;
ret = ReadFile(file, buf, (DWORD) n, &len, NULL); ret = ReadFile(file, buf, (DWORD) n, &len, NULL);
os_n_pending_reads--;
os_mutex_exit(os_file_seek_mutexes[i]); 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) { if (ret && len == n) {
return(TRUE); return(TRUE);
} }
...@@ -2225,12 +2257,8 @@ try_again: ...@@ -2225,12 +2257,8 @@ try_again:
os_bytes_read_since_printout += n; os_bytes_read_since_printout += n;
try_again: try_again:
os_n_pending_reads++;
ret = os_file_pread(file, buf, n, offset, offset_high); ret = os_file_pread(file, buf, n, offset, offset_high);
os_n_pending_reads--;
if ((ulint)ret == n) { if ((ulint)ret == n) {
return(TRUE); return(TRUE);
...@@ -2310,6 +2338,10 @@ retry: ...@@ -2310,6 +2338,10 @@ retry:
low = (DWORD) offset; low = (DWORD) offset;
high = (DWORD) offset_high; 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 */ /* Protect the seek / write operation with a mutex */
i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES; i = ((ulint) file) % OS_FILE_N_SEEK_MUTEXES;
...@@ -2321,6 +2353,10 @@ retry: ...@@ -2321,6 +2353,10 @@ retry:
os_mutex_exit(os_file_seek_mutexes[i]); 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); ut_print_timestamp(stderr);
fprintf(stderr, fprintf(stderr,
...@@ -2335,12 +2371,8 @@ retry: ...@@ -2335,12 +2371,8 @@ retry:
return(FALSE); return(FALSE);
} }
os_n_pending_writes++;
ret = WriteFile(file, buf, (DWORD) n, &len, NULL); ret = WriteFile(file, buf, (DWORD) n, &len, NULL);
os_n_pending_writes--;
/* Always do fsync to reduce the probability that when the OS crashes, /* Always do fsync to reduce the probability that when the OS crashes,
a database page is only partially physically written to disk. */ a database page is only partially physically written to disk. */
...@@ -2352,6 +2384,10 @@ retry: ...@@ -2352,6 +2384,10 @@ retry:
os_mutex_exit(os_file_seek_mutexes[i]); 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) { if (ret && len == n) {
return(TRUE); return(TRUE);
...@@ -2402,11 +2438,7 @@ retry: ...@@ -2402,11 +2438,7 @@ retry:
#else #else
ssize_t ret; ssize_t ret;
os_n_pending_writes++;
ret = os_file_pwrite(file, buf, n, offset, offset_high); ret = os_file_pwrite(file, buf, n, offset, offset_high);
os_n_pending_writes--;
if ((ulint)ret == n) { if ((ulint)ret == n) {
......
...@@ -549,6 +549,15 @@ row_ins_cascade_calc_update_vec( ...@@ -549,6 +549,15 @@ row_ins_cascade_calc_update_vec(
default: default:
ut_error; ut_error;
case 1: case 1:
if (UNIV_UNLIKELY(
dtype_get_charset_coll(
dtype_get_prtype(type))
== DATA_MYSQL_BINARY_CHARSET_COLL)) {
/* Do not pad BINARY
columns. */
return(ULINT_UNDEFINED);
}
/* space=0x20 */ /* space=0x20 */
memset(pad_start, 0x20, memset(pad_start, 0x20,
pad_end - pad_start); pad_end - pad_start);
......
...@@ -1436,8 +1436,9 @@ trx_register_new_rec_lock() to store the information which new record locks ...@@ -1436,8 +1436,9 @@ 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, 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 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 in the case of an UPDATE or a DELETE statement, where the row lock is of the
LOCK_X type. LOCK_X or LOCK_S type.
Thus, this implements a 'mini-rollback' that releases the latest record
Thus, this implements a 'mini-rollback' that releases the latest record
locks we set. */ locks we set. */
int int
...@@ -1474,7 +1475,14 @@ row_unlock_for_mysql( ...@@ -1474,7 +1475,14 @@ row_unlock_for_mysql(
index = btr_pcur_get_btr_cur(pcur)->index; index = btr_pcur_get_btr_cur(pcur)->index;
if (index != NULL && trx_new_rec_locks_contain(trx, index)) { if (UNIV_UNLIKELY(index == NULL)) {
fprintf(stderr,
"InnoDB: Error: Index is not set for persistent cursor.\n");
ut_print_buf(stderr, (const byte*)pcur, sizeof(btr_pcur_t));
ut_error;
}
if (trx_new_rec_locks_contain(trx, index)) {
mtr_start(&mtr); mtr_start(&mtr);
...@@ -1486,11 +1494,7 @@ row_unlock_for_mysql( ...@@ -1486,11 +1494,7 @@ row_unlock_for_mysql(
rec = btr_pcur_get_rec(pcur); rec = btr_pcur_get_rec(pcur);
mutex_enter(&kernel_mutex); lock_rec_unlock(trx, rec, prebuilt->select_lock_type);
lock_rec_reset_and_release_wait(rec);
mutex_exit(&kernel_mutex);
mtr_commit(&mtr); mtr_commit(&mtr);
...@@ -1520,11 +1524,7 @@ row_unlock_for_mysql( ...@@ -1520,11 +1524,7 @@ row_unlock_for_mysql(
rec = btr_pcur_get_rec(clust_pcur); rec = btr_pcur_get_rec(clust_pcur);
mutex_enter(&kernel_mutex); lock_rec_unlock(trx, rec, prebuilt->select_lock_type);
lock_rec_reset_and_release_wait(rec);
mutex_exit(&kernel_mutex);
mtr_commit(&mtr); mtr_commit(&mtr);
} }
......
...@@ -2771,8 +2771,8 @@ row_sel_get_clust_rec_for_mysql( ...@@ -2771,8 +2771,8 @@ row_sel_get_clust_rec_for_mysql(
func_exit: func_exit:
*out_rec = clust_rec; *out_rec = clust_rec;
if (prebuilt->select_lock_type == LOCK_X) { if (prebuilt->select_lock_type != LOCK_NONE) {
/* We may use the cursor in update: store its position */ /* We may use the cursor in unlock: store its position */
btr_pcur_store_position(prebuilt->clust_pcur, mtr); btr_pcur_store_position(prebuilt->clust_pcur, mtr);
} }
...@@ -3972,12 +3972,12 @@ got_row: ...@@ -3972,12 +3972,12 @@ got_row:
/* We have an optimization to save CPU time: if this is a consistent /* 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 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 store the pcur position, because any fetch next or prev will anyway
return 'end of file'. An exception is the MySQL HANDLER command return 'end of file'. Exceptions are locking reads and the MySQL
where the user can move the cursor with PREV or NEXT even after HANDLER command where the user can move the cursor with PREV or NEXT
a unique search. */ even after a unique search. */
if (!unique_search_from_clust_index if (!unique_search_from_clust_index
|| prebuilt->select_lock_type == LOCK_X || prebuilt->select_lock_type != LOCK_NONE
|| prebuilt->used_in_HANDLER) { || prebuilt->used_in_HANDLER) {
/* Inside an update always store the cursor position */ /* Inside an update always store the cursor position */
......
...@@ -794,7 +794,8 @@ trx_commit_off_kernel( ...@@ -794,7 +794,8 @@ trx_commit_off_kernel(
in trx sys header if MySQL binlogging is on or the database in trx sys header if MySQL binlogging is on or the database
server is a MySQL replication slave */ server is a MySQL replication slave */
if (trx->mysql_log_file_name) { if (trx->mysql_log_file_name
&& trx->mysql_log_file_name[0] != '\0') {
trx_sys_update_mysql_binlog_offset( trx_sys_update_mysql_binlog_offset(
trx->mysql_log_file_name, trx->mysql_log_file_name,
trx->mysql_log_offset, trx->mysql_log_offset,
......
This diff is collapsed.
drop table if exists t1,t2;
create table t1 (id int not null, f_id int not null, f int not null,
primary key(f_id, id)) engine=innodb;
create table t2 (id int not null,s_id int not null,s varchar(200),
primary key(id)) engine=innodb;
INSERT INTO t1 VALUES (8, 1, 3);
INSERT INTO t1 VALUES (1, 2, 1);
INSERT INTO t2 VALUES (1, 0, '');
INSERT INTO t2 VALUES (8, 1, '');
commit;
DELETE ml.* FROM t1 AS ml LEFT JOIN t2 AS mm ON (mm.id=ml.id)
WHERE mm.id IS NULL;
select ml.* from t1 as ml left join t2 as mm on (mm.id=ml.id)
where mm.id is null lock in share mode;
id f_id f
drop table t1,t2;
create table t1 (id int not null, f_id int not null, f int not null,
primary key(id),key(f_id)) engine=innodb;
create table t2 (id int not null,s_id int not null,s varchar(200),
primary key(id),key(s_id)) engine=innodb;
INSERT INTO t1 VALUES (8, 1, 3);
INSERT INTO t1 VALUES (1, 2, 1);
INSERT INTO t2 VALUES (1, 0, '');
INSERT INTO t2 VALUES (8, 1, '');
commit;
delete ml.* from t1 as ml left join t2 as mm on (mm.s_id=ml.f_id) where mm.s is null;
select ml.* from t1 as ml left join t2 as mm on (mm.s_id=ml.f_id) where mm.s is null lock in share mode;
id f_id f
drop table t1,t2;
create table t1(a int not null, b int, primary key(a)) engine=innodb;
insert into t1 values(1,1),(2,2),(3,1),(4,2),(5,1),(6,2);
commit;
set autocommit = 0;
select * from t1 lock in share mode;
a b
1 1
2 2
3 1
4 2
5 1
6 2
update t1 set b = 5 where b = 1;
set autocommit = 0;
select * from t1 where a = 2 and b = 2 for update;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
commit;
commit;
drop table t1;
This diff is collapsed.
--innodb_locks_unsafe_for_binlog=true
-- source include/have_innodb.inc
#
# Note that these tests uses a innodb_locks_unsafe_for_binlog option.
#
# Test cases for a bug #15650 DELETE with LEFT JOIN crashes server
#
--disable_warnings
drop table if exists t1,t2;
--enable_warnings
create table t1 (id int not null, f_id int not null, f int not null,
primary key(f_id, id)) engine=innodb;
create table t2 (id int not null,s_id int not null,s varchar(200),
primary key(id)) engine=innodb;
INSERT INTO t1 VALUES (8, 1, 3);
INSERT INTO t1 VALUES (1, 2, 1);
INSERT INTO t2 VALUES (1, 0, '');
INSERT INTO t2 VALUES (8, 1, '');
commit;
DELETE ml.* FROM t1 AS ml LEFT JOIN t2 AS mm ON (mm.id=ml.id)
WHERE mm.id IS NULL;
select ml.* from t1 as ml left join t2 as mm on (mm.id=ml.id)
where mm.id is null lock in share mode;
drop table t1,t2;
create table t1 (id int not null, f_id int not null, f int not null,
primary key(id),key(f_id)) engine=innodb;
create table t2 (id int not null,s_id int not null,s varchar(200),
primary key(id),key(s_id)) engine=innodb;
INSERT INTO t1 VALUES (8, 1, 3);
INSERT INTO t1 VALUES (1, 2, 1);
INSERT INTO t2 VALUES (1, 0, '');
INSERT INTO t2 VALUES (8, 1, '');
commit;
delete ml.* from t1 as ml left join t2 as mm on (mm.s_id=ml.f_id) where mm.s is null;
select ml.* from t1 as ml left join t2 as mm on (mm.s_id=ml.f_id) where mm.s is null lock in share mode;
drop table t1,t2;
#
# Test case for unlock row bug where unlock releases all locks granted for
# a row. Only the latest lock should be released.
#
connect (a,localhost,root,,);
connect (b,localhost,root,,);
connection a;
create table t1(a int not null, b int, primary key(a)) engine=innodb;
insert into t1 values(1,1),(2,2),(3,1),(4,2),(5,1),(6,2);
commit;
set autocommit = 0;
select * from t1 lock in share mode;
update t1 set b = 5 where b = 1;
connection b;
set autocommit = 0;
#
# S-lock to records (2,2),(4,2), and (6,2) should not be released in a update
#
--error 1205
select * from t1 where a = 2 and b = 2 for update;
connection a;
commit;
connection b;
commit;
drop table t1;
disconnect a;
disconnect b;
...@@ -1218,7 +1218,7 @@ innobase_init(void) ...@@ -1218,7 +1218,7 @@ innobase_init(void)
"innobase_buffer_pool_size can't be over 4GB" "innobase_buffer_pool_size can't be over 4GB"
" on 32-bit systems"); " on 32-bit systems");
goto error; DBUG_RETURN(0);
} }
if (innobase_log_file_size > UINT_MAX32) { if (innobase_log_file_size > UINT_MAX32) {
...@@ -1226,7 +1226,7 @@ innobase_init(void) ...@@ -1226,7 +1226,7 @@ innobase_init(void)
"innobase_log_file_size can't be over 4GB" "innobase_log_file_size can't be over 4GB"
" on 32-bit systems"); " on 32-bit systems");
goto error; DBUG_RETURN(0);
} }
} }
...@@ -1391,6 +1391,7 @@ innobase_init(void) ...@@ -1391,6 +1391,7 @@ innobase_init(void)
ut_a(DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL == ut_a(DATA_MYSQL_LATIN1_SWEDISH_CHARSET_COLL ==
my_charset_latin1.number); 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 /* Store the latin1_swedish_ci character ordering table to InnoDB. For
non-latin1_swedish_ci charsets we use the MySQL comparison functions, non-latin1_swedish_ci charsets we use the MySQL comparison functions,
...@@ -2847,8 +2848,6 @@ ha_innobase::store_key_val_for_row( ...@@ -2847,8 +2848,6 @@ ha_innobase::store_key_val_for_row(
char* buff_start = buff; char* buff_start = buff;
enum_field_types mysql_type; enum_field_types mysql_type;
Field* field; Field* field;
ulint blob_len;
byte* blob_data;
ibool is_null; ibool is_null;
DBUG_ENTER("store_key_val_for_row"); DBUG_ENTER("store_key_val_for_row");
...@@ -2903,14 +2902,18 @@ ha_innobase::store_key_val_for_row( ...@@ -2903,14 +2902,18 @@ ha_innobase::store_key_val_for_row(
ulint len; ulint len;
byte* data; byte* data;
ulint key_len; ulint key_len;
ulint true_len;
CHARSET_INFO* cs; CHARSET_INFO* cs;
int error=0; int error=0;
key_len = key_part->length;
if (is_null) { if (is_null) {
buff += key_part->length + 2; buff += key_len + 2;
continue; continue;
} }
cs = field->charset();
lenlen = (ulint) lenlen = (ulint)
(((Field_varstring*)field)->length_bytes); (((Field_varstring*)field)->length_bytes);
...@@ -2920,32 +2923,33 @@ ha_innobase::store_key_val_for_row( ...@@ -2920,32 +2923,33 @@ ha_innobase::store_key_val_for_row(
+ (ulint)get_field_offset(table, field)), + (ulint)get_field_offset(table, field)),
lenlen); 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,
key_len / cs->mbmaxlen,
&error);
}
/* In a column prefix index, we may need to truncate /* In a column prefix index, we may need to truncate
the stored value: */ the stored value: */
cs = key_part->field->charset();
if (cs->mbmaxlen > 1 && key_part->length > 0) {
key_len = (ulint) cs->cset->well_formed_len(cs,
(const char *) data,
(const char *) data + key_part->length,
key_part->length / cs->mbmaxlen,
&error);
} else {
key_len = key_part->length;
}
if (len > key_len) { if (true_len > key_len) {
len = key_len; true_len = key_len;
} }
/* The length in a key value is always stored in 2 /* The length in a key value is always stored in 2
bytes */ bytes */
row_mysql_store_true_var_len((byte*)buff, len, 2); row_mysql_store_true_var_len((byte*)buff, true_len, 2);
buff += 2; buff += 2;
memcpy(buff, data, len); memcpy(buff, data, true_len);
/* Note that we always reserve the maximum possible /* Note that we always reserve the maximum possible
length of the true VARCHAR in the key value, though length of the true VARCHAR in the key value, though
...@@ -2953,7 +2957,7 @@ ha_innobase::store_key_val_for_row( ...@@ -2953,7 +2957,7 @@ ha_innobase::store_key_val_for_row(
actual data. The rest of the space was reset to zero actual data. The rest of the space was reset to zero
in the bzero() call above. */ in the bzero() call above. */
buff += key_part->length; buff += key_len;
} else if (mysql_type == FIELD_TYPE_TINY_BLOB } else if (mysql_type == FIELD_TYPE_TINY_BLOB
|| mysql_type == FIELD_TYPE_MEDIUM_BLOB || mysql_type == FIELD_TYPE_MEDIUM_BLOB
...@@ -2963,58 +2967,66 @@ ha_innobase::store_key_val_for_row( ...@@ -2963,58 +2967,66 @@ ha_innobase::store_key_val_for_row(
CHARSET_INFO* cs; CHARSET_INFO* cs;
ulint key_len; ulint key_len;
ulint len; ulint len;
ulint true_len;
int error=0; int error=0;
ulint blob_len;
byte* blob_data;
ut_a(key_part->key_part_flag & HA_PART_KEY_SEG); ut_a(key_part->key_part_flag & HA_PART_KEY_SEG);
key_len = key_part->length;
if (is_null) { if (is_null) {
buff += key_part->length + 2; buff += key_len + 2;
continue; continue;
} }
cs = field->charset();
blob_data = row_mysql_read_blob_ref(&blob_len, blob_data = row_mysql_read_blob_ref(&blob_len,
(byte*) (record (byte*) (record
+ (ulint)get_field_offset(table, field)), + (ulint)get_field_offset(table, field)),
(ulint) field->pack_length()); (ulint) field->pack_length());
true_len = blob_len;
ut_a(get_field_offset(table, field) ut_a(get_field_offset(table, field)
== key_part->offset); == 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,
key_len / cs->mbmaxlen,
&error);
}
/* All indexes on BLOB and TEXT are column prefix /* All indexes on BLOB and TEXT are column prefix
indexes, and we may need to truncate the data to be indexes, and we may need to truncate the data to be
stored in the key value: */ stored in the key value: */
cs = key_part->field->charset(); if (true_len > key_len) {
true_len = key_len;
if (cs->mbmaxlen > 1 && key_part->length > 0) {
key_len = (ulint) cs->cset->well_formed_len(cs,
(const char *) blob_data,
(const char *) blob_data
+ key_part->length,
key_part->length / cs->mbmaxlen,
&error);
} else {
key_len = key_part->length;
}
if (blob_len > key_len) {
blob_len = key_len;
} }
/* MySQL reserves 2 bytes for the length and the /* MySQL reserves 2 bytes for the length and the
storage of the number is little-endian */ storage of the number is little-endian */
innobase_write_to_2_little_endian( innobase_write_to_2_little_endian(
(byte*)buff, (ulint)blob_len); (byte*)buff, true_len);
buff += 2; buff += 2;
memcpy(buff, blob_data, blob_len); memcpy(buff, blob_data, true_len);
/* Note that we always reserve the maximum possible /* Note that we always reserve the maximum possible
length of the BLOB prefix in the key value. */ length of the BLOB prefix in the key value. */
buff += key_part->length; buff += key_len;
} else { } else {
/* Here we handle all other data types except the /* Here we handle all other data types except the
true VARCHAR, BLOB and TEXT. Note that the column true VARCHAR, BLOB and TEXT. Note that the column
...@@ -3022,38 +3034,64 @@ ha_innobase::store_key_val_for_row( ...@@ -3022,38 +3034,64 @@ ha_innobase::store_key_val_for_row(
index. */ index. */
CHARSET_INFO* cs; CHARSET_INFO* cs;
ulint len; ulint true_len;
ulint key_len;
const mysql_byte* src_start; const mysql_byte* src_start;
int error=0; int error=0;
enum_field_types real_type;
key_len = key_part->length;
if (is_null) { if (is_null) {
buff += key_part->length; buff += key_len;
continue; continue;
} }
cs = key_part->field->charset();
src_start = record + key_part->offset; src_start = record + key_part->offset;
real_type = field->real_type();
true_len = key_len;
if (key_part->length > 0 && cs->mbmaxlen > 1) { /* Character set for the field is defined only
len = (ulint) cs->cset->well_formed_len(cs, to fields whose type is string and real field
(const char *) src_start, type is not enum or set. For these fields check
(const char *) src_start + key_part->length, if character set is multi byte. */
key_part->length / cs->mbmaxlen,
&error); if (real_type != FIELD_TYPE_ENUM
} else { && real_type != FIELD_TYPE_SET
len = key_part->length; && ( 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,
key_len / cs->mbmaxlen,
&error);
}
} }
memcpy(buff, src_start, len); memcpy(buff, src_start, true_len);
buff+=len; buff += true_len;
/* Pad the unused space with spaces */ /* 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 (len < key_part->length) { if (true_len < key_len) {
len = key_part->length - len; ulint pad_len = key_len - true_len;
memset(buff, ' ', len); memset(buff, ' ', pad_len);
buff+=len; buff += pad_len;
} }
} }
} }
...@@ -3770,9 +3808,8 @@ ha_innobase::delete_row( ...@@ -3770,9 +3808,8 @@ ha_innobase::delete_row(
} }
/************************************************************************** /**************************************************************************
Removes a new lock set on a row. This can be called after a row has been read Removes a new lock set on a row. This method does nothing unless the
in the processing of an UPDATE or a DELETE query, if the option option innodb_locks_unsafe_for_binlog is set.*/
innodb_locks_unsafe_for_binlog is set. */
void void
ha_innobase::unlock_row(void) ha_innobase::unlock_row(void)
...@@ -3794,6 +3831,9 @@ ha_innobase::unlock_row(void) ...@@ -3794,6 +3831,9 @@ ha_innobase::unlock_row(void)
if (srv_locks_unsafe_for_binlog) { if (srv_locks_unsafe_for_binlog) {
row_unlock_for_mysql(prebuilt, FALSE); row_unlock_for_mysql(prebuilt, FALSE);
} }
DBUG_VOID_RETURN;
} }
/********************************************************************** /**********************************************************************
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment