Commit e8de094a authored by vasil's avatar vasil

branches/zip:

Add innodb_locks.lock_data column and some relevant tests.
For record locks this column represents the ordering fields of the
locked row in a human readable, SQL-valid, format.

Approved by:	Marko
parent c8bbe754
......@@ -478,6 +478,15 @@ static ST_FIELD_INFO innodb_locks_fields_info[] =
STRUCT_FLD(old_name, ""),
STRUCT_FLD(open_method, SKIP_OPEN_TABLE)},
#define IDX_LOCK_DATA 9
{STRUCT_FLD(field_name, "lock_data"),
STRUCT_FLD(field_length, TRX_I_S_LOCK_DATA_MAX_LEN),
STRUCT_FLD(field_type, MYSQL_TYPE_STRING),
STRUCT_FLD(value, 0),
STRUCT_FLD(field_flags, MY_I_S_MAYBE_NULL),
STRUCT_FLD(old_name, ""),
STRUCT_FLD(open_method, SKIP_OPEN_TABLE)},
END_OF_ST_FIELD_INFO
};
......@@ -550,6 +559,10 @@ fill_innodb_locks_from_cache(
OK(field_store_ulint(fields[IDX_LOCK_REC],
row->lock_rec));
/* lock_data */
OK(field_store_string(fields[IDX_LOCK_DATA],
row->lock_data));
OK(schema_table_store_record(thd, table));
}
......
......@@ -269,6 +269,27 @@ row_search_index_entry(
No new latches may be obtained while the kernel mutex is reserved.
However, the kernel mutex can be reserved while latches are owned. */
/***********************************************************************
Formats the raw data in "data" (in InnoDB on-disk format) using
"dict_field" and writes the result to "buf".
Not more than "buf_size" bytes are written to "buf".
The result is always '\0'-terminated (provided buf_size > 0) and the
number of bytes that were written to "buf" is returned (including the
terminating '\0'). */
ulint
row_raw_format(
/*===========*/
/* out: number of bytes
that were written */
const char* data, /* in: raw data */
ulint data_len, /* in: raw data length
in bytes */
const dict_field_t* dict_field, /* in: index field */
char* buf, /* out: output buffer */
ulint buf_size); /* in: output buffer size
in bytes */
#ifndef UNIV_NONINL
#include "row0row.ic"
#endif
......
......@@ -14,6 +14,10 @@ Created July 17, 2007 Vasil Dimov
#include "univ.i"
#include "ut0ut.h"
/* the maximum length of a string that can be stored in
i_s_locks_row_t::lock_data */
#define TRX_I_S_LOCK_DATA_MAX_LEN 8192
typedef struct i_s_locks_row_struct i_s_locks_row_t;
typedef struct i_s_hash_chain_struct i_s_hash_chain_t;
......@@ -34,6 +38,8 @@ struct i_s_locks_row_struct {
ulint lock_space;
ulint lock_page;
ulint lock_rec;
const char* lock_data;
/* The following are auxiliary and not included in the table */
ullint lock_table_id;
i_s_hash_chain_t hash_chain; /* this object is added to the hash
......
......@@ -83,6 +83,16 @@ memory is read outside the allocated blocks. */
#define UNIV_INIT_MEM_TO_ZERO
*/
/* When this macro is defined then additional test functions will be
compiled. These functions live at the end of each relevant source file
and have "test_" prefix. These functions are not called from anywhere in
the code, they can be called from gdb after
innobase_start_or_create_for_mysql() has executed using the call
command. Not tested on Windows. */
/*
#define UNIV_COMPILE_TEST_FUNCS
*/
#if 0
#define UNIV_DEBUG_VALGRIND /* Enable extra
Valgrind instrumentation */
......
......@@ -110,4 +110,35 @@ ut_dbg_stop_thread(
#define UT_NOT_USED(A) A = A
#ifdef UNIV_COMPILE_TEST_FUNCS
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
/* structure used for recording usage statistics */
typedef struct speedo_struct {
struct rusage ru;
struct timeval tv;
} speedo_t;
/***********************************************************************
Resets a speedo (records the current time in it). */
void
speedo_reset(
/*=========*/
speedo_t* speedo); /* out: speedo */
/***********************************************************************
Shows the time elapsed and usage statistics since the last reset of a
speedo. */
void
speedo_show(
/*========*/
const speedo_t* speedo); /* in: speedo */
#endif /* UNIV_COMPILE_TEST_FUNCS */
#endif
......@@ -220,6 +220,23 @@ ut_raw_to_hex(
char* hex, /* out: hex string */
ulint hex_size); /* in: "hex" size in bytes */
/***********************************************************************
Adds single quotes to the start and end of string and escapes any quotes
by doubling them. Returns the number of bytes that were written to "buf"
(including the terminating '\0'). If buf_size is too small then the
trailing bytes from "str" are discarded. */
UNIV_INLINE
ulint
ut_str_sql_format(
/*==============*/
/* out: number of bytes
that were written */
const char* str, /* in: string */
ulint str_len, /* in: string length in bytes */
char* buf, /* out: output buffer */
ulint buf_size); /* in: output buffer size
in bytes */
#ifndef UNIV_NONINL
#include "ut0mem.ic"
#endif
......
......@@ -7,6 +7,7 @@ Created 5/30/1994 Heikki Tuuri
************************************************************************/
#include "ut0byte.h"
#include "mach0data.h"
UNIV_INLINE
void*
......@@ -190,3 +191,102 @@ ut_raw_to_hex(
return(write_bytes);
}
/***********************************************************************
Adds single quotes to the start and end of string and escapes any quotes
by doubling them. Returns the number of bytes that were written to "buf"
(including the terminating '\0'). If buf_size is too small then the
trailing bytes from "str" are discarded. */
UNIV_INLINE
ulint
ut_str_sql_format(
/*==============*/
/* out: number of bytes
that were written */
const char* str, /* in: string */
ulint str_len, /* in: string length in bytes */
char* buf, /* out: output buffer */
ulint buf_size) /* in: output buffer size
in bytes */
{
ulint str_i;
ulint buf_i;
buf_i = 0;
switch (buf_size) {
case 3:
if (str_len == 0) {
buf[buf_i] = '\'';
buf_i++;
buf[buf_i] = '\'';
buf_i++;
}
/* FALLTHROUGH */
case 2:
case 1:
buf[buf_i] = '\0';
buf_i++;
/* FALLTHROUGH */
case 0:
return(buf_i);
}
/* buf_size >= 4 */
buf[0] = '\'';
buf_i = 1;
for (str_i = 0; str_i < str_len; str_i++) {
char ch;
if (buf_size - buf_i == 2) {
break;
}
ch = str[str_i];
switch (ch) {
case '\0':
if (UNIV_UNLIKELY(buf_size - buf_i < 4)) {
goto func_exit;
}
buf[buf_i] = '\\';
buf_i++;
buf[buf_i] = '0';
buf_i++;
break;
case '\'':
case '\\':
if (UNIV_UNLIKELY(buf_size - buf_i < 4)) {
goto func_exit;
}
buf[buf_i] = ch;
buf_i++;
/* FALLTHROUGH */
default:
buf[buf_i] = ch;
buf_i++;
}
}
func_exit:
buf[buf_i] = '\'';
buf_i++;
buf[buf_i] = '\0';
buf_i++;
return(buf_i);
}
lock_data
'1', 'abc', '''abc', 'abc''', 'a''bc', 'a''bc''', '''abc'''''
'1', 'abc', '''abc', 'abc''', 'a''bc', 'a''bc''', '''abc'''''
'2', 'abc', '"abc', 'abc"', 'a"bc', 'a"bc"', '"abc""'
'2', 'abc', '"abc', 'abc"', 'a"bc', 'a"bc"', '"abc""'
'3', 'abc', '\\abc', 'abc\\', 'a\\bc', 'a\\bc\\', '\\abc\\\\'
'3', 'abc', '\\abc', 'abc\\', 'a\\bc', 'a\\bc\\', '\\abc\\\\'
'4', 'abc', '\0abc', 'abc\0', 'a\0bc', 'a\0bc\0', 'a\0bc\0\0'
'4', 'abc', '\0abc', 'abc\0', 'a\0bc', 'a\0bc\0', 'a\0bc\0\0'
-128, 0, -32768, 0, -8388608, 0, -2147483648, 0, -9223372036854775808, 0
-128, 0, -32768, 0, -8388608, 0, -2147483648, 0, -9223372036854775808, 0
127, 255, 32767, 65535, 8388607, 16777215, 2147483647, 4294967295, 9223372036854775807, 18446744073709551615
127, 255, 32767, 65535, 8388607, 16777215, 2147483647, 4294967295, 9223372036854775807, 18446744073709551615
supremum pseudo-record
supremum pseudo-record
#
# Test that user data is correctly "visualized" in
# INFORMATION_SCHEMA.innodb_locks.lock_data
#
-- source include/have_innodb.inc
-- disable_query_log
-- disable_result_log
SET storage_engine=InnoDB;
-- disable_warnings
DROP TABLE IF EXISTS t_min, t_max;
-- enable_warnings
let $table_def =
(
c01 TINYINT,
c02 TINYINT UNSIGNED,
c03 SMALLINT,
c04 SMALLINT UNSIGNED,
c05 MEDIUMINT,
c06 MEDIUMINT UNSIGNED,
c07 INT,
c08 INT UNSIGNED,
c09 BIGINT,
c10 BIGINT UNSIGNED,
PRIMARY KEY(c01, c02, c03, c04, c05, c06, c07, c08, c09, c10)
);
-- eval CREATE TABLE t_min $table_def;
INSERT INTO t_min VALUES
(-128, 0,
-32768, 0,
-8388608, 0,
-2147483648, 0,
-9223372036854775808, 0);
-- eval CREATE TABLE t_max $table_def;
INSERT INTO t_max VALUES
(127, 255,
32767, 65535,
8388607, 16777215,
2147483647, 4294967295,
9223372036854775807, 18446744073709551615);
CREATE TABLE t_str (
c1 VARCHAR(32),
c2 VARCHAR(32),
c3 VARCHAR(32),
c4 VARCHAR(32),
c5 VARCHAR(32),
c6 VARCHAR(32),
c7 VARCHAR(32),
PRIMARY KEY(c1, c2, c3, c4, c5, c6, c7)
);
INSERT INTO t_str VALUES
('1', 'abc', '''abc', 'abc''', 'a''bc', 'a''bc''', '''abc''''');
INSERT INTO t_str VALUES
('2', 'abc', '"abc', 'abc"', 'a"bc', 'a"bc"', '"abc""');
INSERT INTO t_str VALUES
('3', 'abc', '\\abc', 'abc\\', 'a\\bc', 'a\\bc\\', '\\abc\\\\');
INSERT INTO t_str VALUES
('4', 'abc', 0x00616263, 0x61626300, 0x61006263, 0x6100626300, 0x610062630000);
-- connect (con_lock,localhost,root,,)
-- connect (con_min_trylock,localhost,root,,)
-- connect (con_max_trylock,localhost,root,,)
-- connect (con_str_insert_supremum,localhost,root,,)
-- connect (con_str_lock_row1,localhost,root,,)
-- connect (con_str_lock_row2,localhost,root,,)
-- connect (con_str_lock_row3,localhost,root,,)
-- connect (con_str_lock_row4,localhost,root,,)
-- connect (con_verify_innodb_locks,localhost,root,,)
-- connection con_lock
SET autocommit=0;
SELECT * FROM t_min FOR UPDATE;
SELECT * FROM t_max FOR UPDATE;
SELECT * FROM t_str FOR UPDATE;
-- connection con_min_trylock
-- send
SELECT * FROM t_min FOR UPDATE;
-- connection con_max_trylock
-- send
SELECT * FROM t_max FOR UPDATE;
-- connection con_str_insert_supremum
-- send
INSERT INTO t_str VALUES
('z', 'z', 'z', 'z', 'z', 'z', 'z');
-- connection con_str_lock_row1
-- send
SELECT * FROM t_str WHERE c1 = '1' FOR UPDATE;
-- connection con_str_lock_row2
-- send
SELECT * FROM t_str WHERE c1 = '2' FOR UPDATE;
-- connection con_str_lock_row3
-- send
SELECT * FROM t_str WHERE c1 = '3' FOR UPDATE;
-- connection con_str_lock_row4
-- send
SELECT * FROM t_str WHERE c1 = '4' FOR UPDATE;
# Give time to the above 2 queries to execute before continuing.
# Without this sleep it sometimes happens that the SELECT from innodb_locks
# executes before some of them, resulting in less than expected number
# of rows being selected from innodb_locks.
-- sleep 0.1
-- enable_result_log
-- connection con_verify_innodb_locks
SELECT lock_data FROM INFORMATION_SCHEMA.innodb_locks ORDER BY lock_data;
-- disable_result_log
-- connection default
-- disconnect con_lock
-- disconnect con_min_trylock
-- disconnect con_max_trylock
-- disconnect con_str_insert_supremum
-- disconnect con_str_lock_row1
-- disconnect con_str_lock_row2
-- disconnect con_str_lock_row3
-- disconnect con_str_lock_row4
-- disconnect con_verify_innodb_locks
DROP TABLE t_min, t_max, t_str;
This diff is collapsed.
......@@ -14,11 +14,16 @@ Created July 17, 2007 Vasil Dimov
#include <mysql/plugin.h>
#include "univ.i"
#include "buf0buf.h"
#include "dict0dict.h"
#include "ha0storage.h"
#include "hash0hash.h"
#include "lock0iter.h"
#include "lock0lock.h"
#include "mem0mem.h"
#include "page0page.h"
#include "rem0rec.h"
#include "row0row.h"
#include "srv0srv.h"
#include "sync0rw.h"
#include "sync0sync.h"
......@@ -360,6 +365,163 @@ fill_trx_row(
return(row);
}
/***********************************************************************
Format the nth field of "rec" and put it in "buf". The result is always
'\0'-terminated. Returns the number of bytes that were written to "buf"
(including the terminating '\0'). */
static
ulint
put_nth_field(
/*==========*/
/* out: end of the result */
char* buf, /* out: buffer */
ulint buf_size,/* in: buffer size in bytes */
ulint n, /* in: number of field */
const dict_index_t* index, /* in: index */
const rec_t* rec, /* in: record */
const ulint* offsets)/* in: record offsets, returned
by rec_get_offsets() */
{
const byte* data;
ulint data_len;
dict_field_t* dict_field;
ulint ret;
ut_ad(rec_offs_validate(rec, NULL, offsets));
if (buf_size == 0) {
return(0);
}
ret = 0;
if (n > 0) {
/* we must append ", " before the actual data */
if (buf_size < 3) {
buf[0] = '\0';
return(1);
}
memcpy(buf, ", ", 3);
buf += 2;
buf_size -= 2;
ret += 2;
}
/* now buf_size >= 1 */
data = rec_get_nth_field(rec, offsets, n, &data_len);
dict_field = dict_index_get_nth_field(index, n);
ret += row_raw_format((const char*) data, data_len,
dict_field, buf, buf_size);
return(ret);
}
/***********************************************************************
Fills the "lock_data" member of i_s_locks_row_t object. */
static
void
fill_lock_data(
/*===========*/
const char** lock_data,/* out: "lock_data" to fill */
const lock_t* lock, /* in: lock used to find the data */
ulint heap_no,/* in: rec num used to find the data */
trx_i_s_cache_t* cache) /* in/out: cache where to store
volatile data */
{
mtr_t mtr;
const buf_block_t* block;
const page_t* page;
const rec_t* rec;
ut_a(lock_get_type(lock) == LOCK_REC);
mtr_start(&mtr);
block = buf_page_try_get(lock_rec_get_space_id(lock),
lock_rec_get_page_no(lock),
&mtr);
if (block == NULL) {
*lock_data = NULL;
mtr_commit(&mtr);
return;
}
page = (const page_t*) buf_block_get_frame(block);
rec = page_find_rec_with_heap_no(page, heap_no);
if (page_rec_is_infimum(rec)) {
*lock_data = ha_storage_put_str(cache->storage,
"infimum pseudo-record");
} else if (page_rec_is_supremum(rec)) {
*lock_data = ha_storage_put_str(cache->storage,
"supremum pseudo-record");
} else {
const dict_index_t* index;
ulint n_fields;
mem_heap_t* heap;
ulint offsets_onstack[REC_OFFS_NORMAL_SIZE];
ulint* offsets;
char buf[TRX_I_S_LOCK_DATA_MAX_LEN];
ulint buf_used;
ulint i;
rec_offs_init(offsets_onstack);
offsets = offsets_onstack;
index = lock_rec_get_index(lock);
n_fields = dict_index_get_n_unique(index);
ut_a(n_fields > 0);
heap = NULL;
offsets = rec_get_offsets(rec, index, offsets, n_fields,
&heap);
/* format and store the data */
buf_used = 0;
for (i = 0; i < n_fields; i++) {
buf_used += put_nth_field(
buf + buf_used, sizeof(buf) - buf_used,
i, index, rec, offsets) - 1;
}
*lock_data = (const char*) ha_storage_put(cache->storage,
buf,
buf_used + 1);
if (UNIV_UNLIKELY(heap != NULL)) {
/* this means that rec_get_offsets() has created a new
heap and has stored offsets in it; check that this is
really the case and free the heap */
ut_a(offsets != offsets_onstack);
mem_heap_free(heap);
}
}
mtr_commit(&mtr);
}
/***********************************************************************
Fills i_s_locks_row_t object. Returns its first argument. */
static
......@@ -391,6 +553,8 @@ fill_locks_row(
row->lock_page = lock_rec_get_page_no(lock);
row->lock_rec = heap_no;
fill_lock_data(&row->lock_data, lock, heap_no, cache);
break;
case LOCK_TABLE:
row->lock_index = NULL;
......@@ -399,6 +563,8 @@ fill_locks_row(
row->lock_page = ULINT_UNDEFINED;
row->lock_rec = ULINT_UNDEFINED;
row->lock_data = NULL;
break;
default:
ut_error;
......
......@@ -7,6 +7,7 @@ Created 1/30/1994 Heikki Tuuri
**********************************************************************/
#include "univ.i"
#include "ut0dbg.h"
#if defined(__GNUC__) && (__GNUC__ > 2)
#else
......@@ -96,3 +97,69 @@ ut_dbg_stop_thread(
}
# endif
#endif /* __NETWARE__ */
#ifdef UNIV_COMPILE_TEST_FUNCS
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#ifndef timersub
#define timersub(a, b, r) \
do { \
(r)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(r)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((r)->tv_usec < 0) { \
(r)->tv_sec--; \
(r)->tv_usec += 1000000; \
} \
} while (0)
#endif /* timersub */
/***********************************************************************
Resets a speedo (records the current time in it). */
void
speedo_reset(
/*=========*/
speedo_t* speedo) /* out: speedo */
{
gettimeofday(&speedo->tv, NULL);
getrusage(RUSAGE_SELF, &speedo->ru);
}
/***********************************************************************
Shows the time elapsed and usage statistics since the last reset of a
speedo. */
void
speedo_show(
/*========*/
const speedo_t* speedo) /* in: speedo */
{
struct rusage ru_now;
struct timeval tv_now;
struct timeval tv_diff;
getrusage(RUSAGE_SELF, &ru_now);
gettimeofday(&tv_now, NULL);
#define PRINT_TIMEVAL(prefix, tvp) \
fprintf(stderr, "%s% 5ld.%06ld sec\n", \
prefix, (tvp)->tv_sec, (tvp)->tv_usec)
timersub(&tv_now, &speedo->tv, &tv_diff);
PRINT_TIMEVAL("real", &tv_diff);
timersub(&ru_now.ru_utime, &speedo->ru.ru_utime, &tv_diff);
PRINT_TIMEVAL("user", &tv_diff);
timersub(&ru_now.ru_stime, &speedo->ru.ru_stime, &tv_diff);
PRINT_TIMEVAL("sys ", &tv_diff);
}
#endif /* UNIV_COMPILE_TEST_FUNCS */
......@@ -546,3 +546,83 @@ ut_strreplace(
return(new_str);
}
#ifdef UNIV_COMPILE_TEST_FUNCS
void
test_ut_str_sql_format()
{
char buf[128];
ulint ret;
#define CALL_AND_TEST(str, str_len, buf, buf_size, ret_expected, buf_expected)\
do {\
ibool ok = TRUE;\
memset(buf, 'x', 10);\
buf[10] = '\0';\
fprintf(stderr, "TESTING \"%s\", %lu, %lu\n",\
str, (ulint) str_len, (ulint) buf_size);\
ret = ut_str_sql_format(str, str_len, buf, buf_size);\
if (ret != ret_expected) {\
fprintf(stderr, "expected ret %lu, got %lu\n",\
(ulint) ret_expected, ret);\
ok = FALSE;\
}\
if (strcmp((char*) buf, buf_expected) != 0) {\
fprintf(stderr, "expected buf \"%s\", got \"%s\"\n",\
buf_expected, buf);\
ok = FALSE;\
}\
if (ok) {\
fprintf(stderr, "OK: %lu, \"%s\"\n\n",\
(ulint) ret, buf);\
} else {\
return;\
}\
} while (0)
CALL_AND_TEST("abcd", 4, buf, 0, 0, "xxxxxxxxxx");
CALL_AND_TEST("abcd", 4, buf, 1, 1, "");
CALL_AND_TEST("abcd", 4, buf, 2, 1, "");
CALL_AND_TEST("abcd", 0, buf, 3, 3, "''");
CALL_AND_TEST("abcd", 1, buf, 3, 1, "");
CALL_AND_TEST("abcd", 2, buf, 3, 1, "");
CALL_AND_TEST("abcd", 3, buf, 3, 1, "");
CALL_AND_TEST("abcd", 4, buf, 3, 1, "");
CALL_AND_TEST("abcd", 0, buf, 4, 3, "''");
CALL_AND_TEST("abcd", 1, buf, 4, 4, "'a'");
CALL_AND_TEST("abcd", 2, buf, 4, 4, "'a'");
CALL_AND_TEST("abcd", 3, buf, 4, 4, "'a'");
CALL_AND_TEST("abcd", 4, buf, 4, 4, "'a'");
CALL_AND_TEST("abcde", 5, buf, 4, 4, "'a'");
CALL_AND_TEST("'", 1, buf, 4, 3, "''");
CALL_AND_TEST("''", 2, buf, 4, 3, "''");
CALL_AND_TEST("a'", 2, buf, 4, 4, "'a'");
CALL_AND_TEST("'a", 2, buf, 4, 3, "''");
CALL_AND_TEST("ab", 2, buf, 4, 4, "'a'");
CALL_AND_TEST("abcdef", 0, buf, 5, 3, "''");
CALL_AND_TEST("abcdef", 1, buf, 5, 4, "'a'");
CALL_AND_TEST("abcdef", 2, buf, 5, 5, "'ab'");
CALL_AND_TEST("abcdef", 3, buf, 5, 5, "'ab'");
CALL_AND_TEST("abcdef", 4, buf, 5, 5, "'ab'");
CALL_AND_TEST("abcdef", 5, buf, 5, 5, "'ab'");
CALL_AND_TEST("abcdef", 6, buf, 5, 5, "'ab'");
CALL_AND_TEST("'", 1, buf, 5, 5, "''''");
CALL_AND_TEST("''", 2, buf, 5, 5, "''''");
CALL_AND_TEST("a'", 2, buf, 5, 4, "'a'");
CALL_AND_TEST("'a", 2, buf, 5, 5, "''''");
CALL_AND_TEST("ab", 2, buf, 5, 5, "'ab'");
CALL_AND_TEST("abc", 3, buf, 5, 5, "'ab'");
CALL_AND_TEST("ab", 2, buf, 6, 5, "'ab'");
CALL_AND_TEST("a'b'c", 5, buf, 32, 10, "'a''b''c'");
CALL_AND_TEST("a'b'c'", 6, buf, 32, 12, "'a''b''c'''");
}
#endif /* UNIV_COMPILE_TEST_FUNCS */
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