Commit 5aa9a391 authored by heikki@hundin.mysql.fi's avatar heikki@hundin.mysql.fi
Browse files

row0mysql.c:

  Add some diagnostic code to track BLOB bugs if they were not already fixed with the change in row0sel.c
row0sel.c:
  Fix a seg fault which was caused inside MySQL because InnoDB when returning a BLOB value did not initialize the length and the data pointer of a BLOB whose value is the SQL NULL; also fix a very improbable race condition which could occur if a row with an externally stored BLOB was fetched using the adaptive hash index
parent c50f6a42
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -28,6 +28,9 @@ Created 9/17/2000 Heikki Tuuri
#include "rem0cmp.h"
#include "log0log.h"

/* A dummy variable used to fool the compiler */
ibool	row_mysql_identically_false	= FALSE;

/* List of tables we should drop in background. ALTER TABLE in MySQL requires
that the table handler can drop the table in background when there are no
queries to it any more. Protected by the kernel mutex. */
@@ -67,11 +70,34 @@ row_mysql_store_blob_ref(
	byte*	data,		/* in: BLOB data */
	ulint	len)		/* in: BLOB length */
{
	ulint	sum	= 0;
	ulint	i;

	/* In dest there are 1 - 4 bytes reserved for the BLOB length,
	and after that 8 bytes reserved for the pointer to the data.
	In 32-bit architectures we only use the first 4 bytes of the pointer
	slot. */

	ut_a(col_len - 8 > 1 || len < 256);
	ut_a(col_len - 8 > 2 || len < 256 * 256);
	ut_a(col_len - 8 > 3 || len < 256 * 256 * 256);

	/* We try to track an elusive bug which probably was fixed
	May 9, 2002, but better be sure: we probe the data buffer
	to make sure it is in valid allocated memory */

	for (i = 0; i < len; i++) {

		sum += (ulint)(data + i);
	}

	/* The variable below is identically false, we just fool the
	compiler to not optimize away our loop */
	if (row_mysql_identically_false) {

		printf("Sum %lu\n", sum);
	}

	mach_write_to_n_little_endian(dest, col_len - 8, len);

	ut_memcpy(dest + col_len - 8, (byte*)&data, sizeof(byte*));	
+37 −7
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ Created 12/19/1997 Heikki Tuuri
#include "pars0pars.h"
#include "row0mysql.h"

byte	row_sel_dummy_byte;

/* Maximum number of rows to prefetch; MySQL interface has another parameter */
#define SEL_MAX_N_PREFETCH	16

@@ -2070,13 +2072,11 @@ row_sel_store_mysql_rec(
		data = rec_get_nth_field(rec, templ->rec_field_no, &len);

		if (rec_get_nth_field_extern_bit(rec, templ->rec_field_no)) {

			/* Copy an externally stored field to the temporary
			heap */

			if (prebuilt->trx->has_search_latch) {
				rw_lock_s_unlock(&btr_search_latch);
				prebuilt->trx->has_search_latch = FALSE;
			}
			ut_a(!prebuilt->trx->has_search_latch);

			extern_field_heap = mem_heap_create(UNIV_PAGE_SIZE);

@@ -2090,6 +2090,8 @@ row_sel_store_mysql_rec(
		if (len != UNIV_SQL_NULL) {
			if (templ->type == DATA_BLOB) {

				ut_a(prebuilt->templ_contains_blob);
				
				/* Copy the BLOB data to the BLOB
				heap of prebuilt */

@@ -2115,6 +2117,21 @@ row_sel_store_mysql_rec(
				extern_field_heap = NULL;
 			}
		} else {
			/* MySQL sometimes seems to copy the 'data'
			pointed to by a BLOB field even if the field
			has been marked to contain the SQL NULL value.
			This caused seg faults reported by two users.
			Set the BLOB length to 0 and the data pointer
			to a dummy allocated mem address to avoid
			a seg fault. */

			if (templ->type == DATA_BLOB) {
				row_sel_field_store_in_mysql_format(
				mysql_rec + templ->mysql_col_offset,
				templ->mysql_col_len, &row_sel_dummy_byte,
				0, templ->type, templ->is_unsigned);
			}

			if (!templ->mysql_null_bit_mask) {
				fprintf(stderr,
"InnoDB: Error: trying to return an SQL NULL field in a non-null\n"
@@ -2368,6 +2385,7 @@ row_sel_push_cache_row_for_mysql(
	ulint	i;

	ut_ad(prebuilt->n_fetch_cached < MYSQL_FETCH_CACHE_SIZE);
	ut_a(!prebuilt->templ_contains_blob);

	if (prebuilt->fetch_cache[0] == NULL) {
		/* Allocate memory for the fetch cache */
@@ -2408,6 +2426,7 @@ row_sel_try_search_shortcut_for_mysql(
	rec_t*		rec;
	
	ut_ad(index->type & DICT_CLUSTERED);
	ut_ad(!prebuilt->templ_contains_blob);

	btr_pcur_open_with_no_init(index, search_tuple, PAGE_CUR_GE,
					BTR_SEARCH_LEAF, pcur,
@@ -2590,8 +2609,16 @@ row_search_for_mysql(

	mtr_start(&mtr);

	if (match_mode == ROW_SEL_EXACT && index->type & DICT_UNIQUE
	/* Since we must release the search system latch when we retrieve an
	externally stored field, we cannot use the adaptive hash index in a
	search in the case the row may be long and there may be externally
	stored fields */
	
	if (match_mode == ROW_SEL_EXACT
		&& index->type & DICT_UNIQUE
		&& index->type & DICT_CLUSTERED
		&& !prebuilt->templ_contains_blob
		&& (prebuilt->mysql_row_len < UNIV_PAGE_SIZE / 8)
		&& dtuple_get_n_fields(search_tuple)
				== dict_index_get_n_unique(index)) {

@@ -2944,15 +2971,18 @@ row_search_for_mysql(
	/* We found a qualifying row */
	
	if (prebuilt->n_rows_fetched >= MYSQL_FETCH_CACHE_THRESHOLD
			&& !prebuilt->templ_contains_blob
			&& prebuilt->select_lock_type == LOCK_NONE
			&& !prebuilt->templ_contains_blob
			&& !prebuilt->clust_index_was_generated
	                && prebuilt->template_type
	                                 != ROW_MYSQL_DUMMY_TEMPLATE) {

		/* Inside an update, for example, we do not cache rows,
		since we may use the cursor position to do the actual
		update, that is why we require ...lock_type == LOCK_NONE */
		update, that is why we require ...lock_type == LOCK_NONE.
		Since we keep space in prebuilt only for the BLOBs of
		a single row, we cannot cache rows in the case there
		are BLOBs in the fields to be fetched. */

		row_sel_push_cache_row_for_mysql(prebuilt, rec);