mirror of
https://github.com/MariaDB/server.git
synced 2025-01-22 14:54:20 +01:00
48075f147c
Fixed the issue where an unchanged blob column that is part of the primary key as well is lost if it is externally stored. The fix is provided by Marko. Reviewed by: Sunny
1225 lines
32 KiB
C
1225 lines
32 KiB
C
/******************************************************
|
|
General row routines
|
|
|
|
(c) 1996 Innobase Oy
|
|
|
|
Created 4/20/1996 Heikki Tuuri
|
|
*******************************************************/
|
|
|
|
#include "row0row.h"
|
|
|
|
#ifdef UNIV_NONINL
|
|
#include "row0row.ic"
|
|
#endif
|
|
|
|
#include "data0type.h"
|
|
#include "dict0dict.h"
|
|
#include "btr0btr.h"
|
|
#include "ha_prototypes.h"
|
|
#include "mach0data.h"
|
|
#include "trx0rseg.h"
|
|
#include "trx0trx.h"
|
|
#include "trx0roll.h"
|
|
#include "trx0undo.h"
|
|
#include "trx0purge.h"
|
|
#include "trx0rec.h"
|
|
#include "que0que.h"
|
|
#include "row0ext.h"
|
|
#include "row0upd.h"
|
|
#include "rem0cmp.h"
|
|
#include "read0read.h"
|
|
#include "ut0mem.h"
|
|
|
|
/*************************************************************************
|
|
Gets the offset of trx id field, in bytes relative to the origin of
|
|
a clustered index record. */
|
|
UNIV_INTERN
|
|
ulint
|
|
row_get_trx_id_offset(
|
|
/*==================*/
|
|
/* out: offset of DATA_TRX_ID */
|
|
const rec_t* rec __attribute__((unused)),
|
|
/* in: record */
|
|
dict_index_t* index, /* in: clustered index */
|
|
const ulint* offsets)/* in: rec_get_offsets(rec, index) */
|
|
{
|
|
ulint pos;
|
|
ulint offset;
|
|
ulint len;
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
|
|
pos = dict_index_get_sys_col_pos(index, DATA_TRX_ID);
|
|
|
|
offset = rec_get_nth_field_offs(offsets, pos, &len);
|
|
|
|
ut_ad(len == DATA_TRX_ID_LEN);
|
|
|
|
return(offset);
|
|
}
|
|
|
|
/*********************************************************************
|
|
When an insert or purge to a table is performed, this function builds
|
|
the entry to be inserted into or purged from an index on the table. */
|
|
UNIV_INTERN
|
|
dtuple_t*
|
|
row_build_index_entry(
|
|
/*==================*/
|
|
/* out: index entry which should be
|
|
inserted or purged, or NULL if the
|
|
externally stored columns in the
|
|
clustered index record are unavailable
|
|
and ext != NULL */
|
|
const dtuple_t* row, /* in: row which should be
|
|
inserted or purged */
|
|
row_ext_t* ext, /* in: externally stored column prefixes,
|
|
or NULL */
|
|
dict_index_t* index, /* in: index on the table */
|
|
mem_heap_t* heap) /* in: memory heap from which the memory for
|
|
the index entry is allocated */
|
|
{
|
|
dtuple_t* entry;
|
|
ulint entry_len;
|
|
ulint i;
|
|
|
|
ut_ad(row && index && heap);
|
|
ut_ad(dtuple_check_typed(row));
|
|
|
|
entry_len = dict_index_get_n_fields(index);
|
|
entry = dtuple_create(heap, entry_len);
|
|
|
|
if (UNIV_UNLIKELY(index->type & DICT_UNIVERSAL)) {
|
|
dtuple_set_n_fields_cmp(entry, entry_len);
|
|
/* There may only be externally stored columns
|
|
in a clustered index B-tree of a user table. */
|
|
ut_a(!ext);
|
|
} else {
|
|
dtuple_set_n_fields_cmp(
|
|
entry, dict_index_get_n_unique_in_tree(index));
|
|
}
|
|
|
|
for (i = 0; i < entry_len; i++) {
|
|
const dict_field_t* ind_field
|
|
= dict_index_get_nth_field(index, i);
|
|
const dict_col_t* col
|
|
= ind_field->col;
|
|
ulint col_no
|
|
= dict_col_get_no(col);
|
|
dfield_t* dfield
|
|
= dtuple_get_nth_field(entry, i);
|
|
const dfield_t* dfield2
|
|
= dtuple_get_nth_field(row, col_no);
|
|
ulint len
|
|
= dfield_get_len(dfield2);
|
|
|
|
dfield_copy(dfield, dfield2);
|
|
|
|
if (dfield_is_null(dfield) || ind_field->prefix_len == 0) {
|
|
continue;
|
|
}
|
|
|
|
/* If a column prefix index, take only the prefix.
|
|
Prefix-indexed columns may be externally stored. */
|
|
ut_ad(col->ord_part);
|
|
|
|
if (UNIV_LIKELY_NULL(ext)) {
|
|
/* See if the column is stored externally. */
|
|
const byte* buf = row_ext_lookup(ext, col_no,
|
|
&len);
|
|
if (UNIV_LIKELY_NULL(buf)) {
|
|
if (UNIV_UNLIKELY(buf == field_ref_zero)) {
|
|
return(NULL);
|
|
}
|
|
dfield_set_data(dfield, buf, len);
|
|
}
|
|
} else if (dfield_is_ext(dfield)) {
|
|
ut_a(len >= BTR_EXTERN_FIELD_REF_SIZE);
|
|
len -= BTR_EXTERN_FIELD_REF_SIZE;
|
|
ut_a(ind_field->prefix_len <= len
|
|
|| dict_index_is_clust(index));
|
|
}
|
|
|
|
len = dtype_get_at_most_n_mbchars(
|
|
col->prtype, col->mbminlen, col->mbmaxlen,
|
|
ind_field->prefix_len, len, dfield_get_data(dfield));
|
|
dfield_set_len(dfield, len);
|
|
}
|
|
|
|
ut_ad(dtuple_check_typed(entry));
|
|
|
|
return(entry);
|
|
}
|
|
|
|
/***********************************************************************
|
|
An inverse function to row_build_index_entry. Builds a row from a
|
|
record in a clustered index. */
|
|
UNIV_INTERN
|
|
dtuple_t*
|
|
row_build(
|
|
/*======*/
|
|
/* out, own: row built;
|
|
see the NOTE below! */
|
|
ulint type, /* in: ROW_COPY_POINTERS or
|
|
ROW_COPY_DATA; the latter
|
|
copies also the data fields to
|
|
heap while the first only
|
|
places pointers to data fields
|
|
on the index page, and thus is
|
|
more efficient */
|
|
const dict_index_t* index, /* in: clustered index */
|
|
const rec_t* rec, /* in: record in the clustered
|
|
index; NOTE: in the case
|
|
ROW_COPY_POINTERS the data
|
|
fields in the row will point
|
|
directly into this record,
|
|
therefore, the buffer page of
|
|
this record must be at least
|
|
s-latched and the latch held
|
|
as long as the row dtuple is used! */
|
|
const ulint* offsets,/* in: rec_get_offsets(rec,index)
|
|
or NULL, in which case this function
|
|
will invoke rec_get_offsets() */
|
|
const dict_table_t* col_table,
|
|
/* in: table, to check which
|
|
externally stored columns
|
|
occur in the ordering columns
|
|
of an index, or NULL if
|
|
index->table should be
|
|
consulted instead */
|
|
row_ext_t** ext, /* out, own: cache of
|
|
externally stored column
|
|
prefixes, or NULL */
|
|
mem_heap_t* heap) /* in: memory heap from which
|
|
the memory needed is allocated */
|
|
{
|
|
dtuple_t* row;
|
|
const dict_table_t* table;
|
|
ulint n_fields;
|
|
ulint n_ext_cols;
|
|
ulint* ext_cols = NULL; /* remove warning */
|
|
ulint len;
|
|
ulint row_len;
|
|
byte* buf;
|
|
ulint i;
|
|
ulint j;
|
|
mem_heap_t* tmp_heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
rec_offs_init(offsets_);
|
|
|
|
ut_ad(index && rec && heap);
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
if (!offsets) {
|
|
offsets = rec_get_offsets(rec, index, offsets_,
|
|
ULINT_UNDEFINED, &tmp_heap);
|
|
} else {
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
}
|
|
|
|
if (type != ROW_COPY_POINTERS) {
|
|
/* Take a copy of rec to heap */
|
|
buf = mem_heap_alloc(heap, rec_offs_size(offsets));
|
|
rec = rec_copy(buf, rec, offsets);
|
|
/* Avoid a debug assertion in rec_offs_validate(). */
|
|
rec_offs_make_valid(rec, index, (ulint*) offsets);
|
|
}
|
|
|
|
table = index->table;
|
|
row_len = dict_table_get_n_cols(table);
|
|
|
|
row = dtuple_create(heap, row_len);
|
|
|
|
dict_table_copy_types(row, table);
|
|
|
|
dtuple_set_info_bits(row, rec_get_info_bits(
|
|
rec, dict_table_is_comp(table)));
|
|
|
|
n_fields = rec_offs_n_fields(offsets);
|
|
n_ext_cols = rec_offs_n_extern(offsets);
|
|
if (n_ext_cols) {
|
|
ext_cols = mem_heap_alloc(heap, n_ext_cols * sizeof *ext_cols);
|
|
}
|
|
|
|
for (i = j = 0; i < n_fields; i++) {
|
|
dict_field_t* ind_field
|
|
= dict_index_get_nth_field(index, i);
|
|
const dict_col_t* col
|
|
= dict_field_get_col(ind_field);
|
|
ulint col_no
|
|
= dict_col_get_no(col);
|
|
dfield_t* dfield
|
|
= dtuple_get_nth_field(row, col_no);
|
|
|
|
if (ind_field->prefix_len == 0) {
|
|
|
|
const byte* field = rec_get_nth_field(
|
|
rec, offsets, i, &len);
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
}
|
|
|
|
if (rec_offs_nth_extern(offsets, i)) {
|
|
dfield_set_ext(dfield);
|
|
|
|
if (UNIV_LIKELY_NULL(col_table)) {
|
|
ut_a(col_no
|
|
< dict_table_get_n_cols(col_table));
|
|
col = dict_table_get_nth_col(
|
|
col_table, col_no);
|
|
}
|
|
|
|
if (col->ord_part) {
|
|
/* We will have to fetch prefixes of
|
|
externally stored columns that are
|
|
referenced by column prefixes. */
|
|
ext_cols[j++] = col_no;
|
|
}
|
|
}
|
|
}
|
|
|
|
ut_ad(dtuple_check_typed(row));
|
|
|
|
if (j) {
|
|
*ext = row_ext_create(j, ext_cols, row,
|
|
dict_table_zip_size(index->table),
|
|
heap);
|
|
} else {
|
|
*ext = NULL;
|
|
}
|
|
|
|
if (tmp_heap) {
|
|
mem_heap_free(tmp_heap);
|
|
}
|
|
|
|
return(row);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Converts an index record to a typed data tuple. */
|
|
UNIV_INTERN
|
|
dtuple_t*
|
|
row_rec_to_index_entry_low(
|
|
/*=======================*/
|
|
/* out: index entry built; does not
|
|
set info_bits, and the data fields in
|
|
the entry will point directly to rec */
|
|
const rec_t* rec, /* in: record in the index */
|
|
const dict_index_t* index, /* in: index */
|
|
const ulint* offsets,/* in: rec_get_offsets(rec, index) */
|
|
ulint* n_ext, /* out: number of externally
|
|
stored columns */
|
|
mem_heap_t* heap) /* in: memory heap from which
|
|
the memory needed is allocated */
|
|
{
|
|
dtuple_t* entry;
|
|
dfield_t* dfield;
|
|
ulint i;
|
|
const byte* field;
|
|
ulint len;
|
|
ulint rec_len;
|
|
|
|
ut_ad(rec && heap && index);
|
|
/* Because this function may be invoked by row0merge.c
|
|
on a record whose header is in different format, the check
|
|
rec_offs_validate(rec, index, offsets) must be avoided here. */
|
|
ut_ad(n_ext);
|
|
*n_ext = 0;
|
|
|
|
rec_len = rec_offs_n_fields(offsets);
|
|
|
|
entry = dtuple_create(heap, rec_len);
|
|
|
|
dtuple_set_n_fields_cmp(entry,
|
|
dict_index_get_n_unique_in_tree(index));
|
|
ut_ad(rec_len == dict_index_get_n_fields(index));
|
|
|
|
dict_index_copy_types(entry, index, rec_len);
|
|
|
|
for (i = 0; i < rec_len; i++) {
|
|
|
|
dfield = dtuple_get_nth_field(entry, i);
|
|
field = rec_get_nth_field(rec, offsets, i, &len);
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
if (rec_offs_nth_extern(offsets, i)) {
|
|
dfield_set_ext(dfield);
|
|
(*n_ext)++;
|
|
}
|
|
}
|
|
|
|
ut_ad(dtuple_check_typed(entry));
|
|
|
|
return(entry);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Converts an index record to a typed data tuple. NOTE that externally
|
|
stored (often big) fields are NOT copied to heap. */
|
|
UNIV_INTERN
|
|
dtuple_t*
|
|
row_rec_to_index_entry(
|
|
/*===================*/
|
|
/* out, own: index entry
|
|
built; see the NOTE below! */
|
|
ulint type, /* in: ROW_COPY_DATA, or
|
|
ROW_COPY_POINTERS: the former
|
|
copies also the data fields to
|
|
heap as the latter only places
|
|
pointers to data fields on the
|
|
index page */
|
|
const rec_t* rec, /* in: record in the index;
|
|
NOTE: in the case
|
|
ROW_COPY_POINTERS the data
|
|
fields in the row will point
|
|
directly into this record,
|
|
therefore, the buffer page of
|
|
this record must be at least
|
|
s-latched and the latch held
|
|
as long as the dtuple is used! */
|
|
const dict_index_t* index, /* in: index */
|
|
ulint* offsets,/* in/out: rec_get_offsets(rec) */
|
|
ulint* n_ext, /* out: number of externally
|
|
stored columns */
|
|
mem_heap_t* heap) /* in: memory heap from which
|
|
the memory needed is allocated */
|
|
{
|
|
dtuple_t* entry;
|
|
byte* buf;
|
|
|
|
ut_ad(rec && heap && index);
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
|
|
if (type == ROW_COPY_DATA) {
|
|
/* Take a copy of rec to heap */
|
|
buf = mem_heap_alloc(heap, rec_offs_size(offsets));
|
|
rec = rec_copy(buf, rec, offsets);
|
|
/* Avoid a debug assertion in rec_offs_validate(). */
|
|
rec_offs_make_valid(rec, index, offsets);
|
|
}
|
|
|
|
entry = row_rec_to_index_entry_low(rec, index, offsets, n_ext, heap);
|
|
|
|
dtuple_set_info_bits(entry,
|
|
rec_get_info_bits(rec, rec_offs_comp(offsets)));
|
|
|
|
return(entry);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Builds from a secondary index record a row reference with which we can
|
|
search the clustered index record. */
|
|
UNIV_INTERN
|
|
dtuple_t*
|
|
row_build_row_ref(
|
|
/*==============*/
|
|
/* out, own: row reference built; see the
|
|
NOTE below! */
|
|
ulint type, /* in: ROW_COPY_DATA, or ROW_COPY_POINTERS:
|
|
the former copies also the data fields to
|
|
heap, whereas the latter only places pointers
|
|
to data fields on the index page */
|
|
dict_index_t* index, /* in: secondary index */
|
|
const rec_t* rec, /* in: record in the index;
|
|
NOTE: in the case ROW_COPY_POINTERS
|
|
the data fields in the row will point
|
|
directly into this record, therefore,
|
|
the buffer page of this record must be
|
|
at least s-latched and the latch held
|
|
as long as the row reference is used! */
|
|
mem_heap_t* heap) /* in: memory heap from which the memory
|
|
needed is allocated */
|
|
{
|
|
dict_table_t* table;
|
|
dict_index_t* clust_index;
|
|
dfield_t* dfield;
|
|
dtuple_t* ref;
|
|
const byte* field;
|
|
ulint len;
|
|
ulint ref_len;
|
|
ulint pos;
|
|
byte* buf;
|
|
ulint clust_col_prefix_len;
|
|
ulint i;
|
|
mem_heap_t* tmp_heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
ulint* offsets = offsets_;
|
|
rec_offs_init(offsets_);
|
|
|
|
ut_ad(index && rec && heap);
|
|
ut_ad(!dict_index_is_clust(index));
|
|
|
|
offsets = rec_get_offsets(rec, index, offsets,
|
|
ULINT_UNDEFINED, &tmp_heap);
|
|
/* Secondary indexes must not contain externally stored columns. */
|
|
ut_ad(!rec_offs_any_extern(offsets));
|
|
|
|
if (type == ROW_COPY_DATA) {
|
|
/* Take a copy of rec to heap */
|
|
|
|
buf = mem_heap_alloc(heap, rec_offs_size(offsets));
|
|
|
|
rec = rec_copy(buf, rec, offsets);
|
|
/* Avoid a debug assertion in rec_offs_validate(). */
|
|
rec_offs_make_valid(rec, index, offsets);
|
|
}
|
|
|
|
table = index->table;
|
|
|
|
clust_index = dict_table_get_first_index(table);
|
|
|
|
ref_len = dict_index_get_n_unique(clust_index);
|
|
|
|
ref = dtuple_create(heap, ref_len);
|
|
|
|
dict_index_copy_types(ref, clust_index, ref_len);
|
|
|
|
for (i = 0; i < ref_len; i++) {
|
|
dfield = dtuple_get_nth_field(ref, i);
|
|
|
|
pos = dict_index_get_nth_field_pos(index, clust_index, i);
|
|
|
|
ut_a(pos != ULINT_UNDEFINED);
|
|
|
|
field = rec_get_nth_field(rec, offsets, pos, &len);
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
/* If the primary key contains a column prefix, then the
|
|
secondary index may contain a longer prefix of the same
|
|
column, or the full column, and we must adjust the length
|
|
accordingly. */
|
|
|
|
clust_col_prefix_len = dict_index_get_nth_field(
|
|
clust_index, i)->prefix_len;
|
|
|
|
if (clust_col_prefix_len > 0) {
|
|
if (len != UNIV_SQL_NULL) {
|
|
|
|
const dtype_t* dtype
|
|
= dfield_get_type(dfield);
|
|
|
|
dfield_set_len(dfield,
|
|
dtype_get_at_most_n_mbchars(
|
|
dtype->prtype,
|
|
dtype->mbminlen,
|
|
dtype->mbmaxlen,
|
|
clust_col_prefix_len,
|
|
len, (char*) field));
|
|
}
|
|
}
|
|
}
|
|
|
|
ut_ad(dtuple_check_typed(ref));
|
|
if (tmp_heap) {
|
|
mem_heap_free(tmp_heap);
|
|
}
|
|
|
|
return(ref);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Builds from a secondary index record a row reference with which we can
|
|
search the clustered index record. */
|
|
UNIV_INTERN
|
|
void
|
|
row_build_row_ref_in_tuple(
|
|
/*=======================*/
|
|
dtuple_t* ref, /* in/out: row reference built;
|
|
see the NOTE below! */
|
|
const rec_t* rec, /* in: record in the index;
|
|
NOTE: the data fields in ref
|
|
will point directly into this
|
|
record, therefore, the buffer
|
|
page of this record must be at
|
|
least s-latched and the latch
|
|
held as long as the row
|
|
reference is used! */
|
|
const dict_index_t* index, /* in: secondary index */
|
|
ulint* offsets,/* in: rec_get_offsets(rec, index)
|
|
or NULL */
|
|
trx_t* trx) /* in: transaction */
|
|
{
|
|
const dict_index_t* clust_index;
|
|
dfield_t* dfield;
|
|
const byte* field;
|
|
ulint len;
|
|
ulint ref_len;
|
|
ulint pos;
|
|
ulint clust_col_prefix_len;
|
|
ulint i;
|
|
mem_heap_t* heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
rec_offs_init(offsets_);
|
|
|
|
ut_a(ref);
|
|
ut_a(index);
|
|
ut_a(rec);
|
|
ut_ad(!dict_index_is_clust(index));
|
|
|
|
if (UNIV_UNLIKELY(!index->table)) {
|
|
fputs("InnoDB: table ", stderr);
|
|
notfound:
|
|
ut_print_name(stderr, trx, TRUE, index->table_name);
|
|
fputs(" for index ", stderr);
|
|
ut_print_name(stderr, trx, FALSE, index->name);
|
|
fputs(" not found\n", stderr);
|
|
ut_error;
|
|
}
|
|
|
|
clust_index = dict_table_get_first_index(index->table);
|
|
|
|
if (UNIV_UNLIKELY(!clust_index)) {
|
|
fputs("InnoDB: clust index for table ", stderr);
|
|
goto notfound;
|
|
}
|
|
|
|
if (!offsets) {
|
|
offsets = rec_get_offsets(rec, index, offsets_,
|
|
ULINT_UNDEFINED, &heap);
|
|
} else {
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
}
|
|
|
|
/* Secondary indexes must not contain externally stored columns. */
|
|
ut_ad(!rec_offs_any_extern(offsets));
|
|
ref_len = dict_index_get_n_unique(clust_index);
|
|
|
|
ut_ad(ref_len == dtuple_get_n_fields(ref));
|
|
|
|
dict_index_copy_types(ref, clust_index, ref_len);
|
|
|
|
for (i = 0; i < ref_len; i++) {
|
|
dfield = dtuple_get_nth_field(ref, i);
|
|
|
|
pos = dict_index_get_nth_field_pos(index, clust_index, i);
|
|
|
|
ut_a(pos != ULINT_UNDEFINED);
|
|
|
|
field = rec_get_nth_field(rec, offsets, pos, &len);
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
/* If the primary key contains a column prefix, then the
|
|
secondary index may contain a longer prefix of the same
|
|
column, or the full column, and we must adjust the length
|
|
accordingly. */
|
|
|
|
clust_col_prefix_len = dict_index_get_nth_field(
|
|
clust_index, i)->prefix_len;
|
|
|
|
if (clust_col_prefix_len > 0) {
|
|
if (len != UNIV_SQL_NULL) {
|
|
|
|
const dtype_t* dtype
|
|
= dfield_get_type(dfield);
|
|
|
|
dfield_set_len(dfield,
|
|
dtype_get_at_most_n_mbchars(
|
|
dtype->prtype,
|
|
dtype->mbminlen,
|
|
dtype->mbmaxlen,
|
|
clust_col_prefix_len,
|
|
len, (char*) field));
|
|
}
|
|
}
|
|
}
|
|
|
|
ut_ad(dtuple_check_typed(ref));
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
From a row build a row reference with which we can search the clustered
|
|
index record. */
|
|
UNIV_INTERN
|
|
void
|
|
row_build_row_ref_from_row(
|
|
/*=======================*/
|
|
dtuple_t* ref, /* in/out: row reference built;
|
|
see the NOTE below!
|
|
ref must have the right number
|
|
of fields! */
|
|
const dict_table_t* table, /* in: table */
|
|
const dtuple_t* row) /* in: row
|
|
NOTE: the data fields in ref will point
|
|
directly into data of this row */
|
|
{
|
|
const dict_index_t* clust_index;
|
|
ulint ref_len;
|
|
ulint i;
|
|
|
|
ut_ad(ref && table && row);
|
|
|
|
clust_index = dict_table_get_first_index(table);
|
|
|
|
ref_len = dict_index_get_n_unique(clust_index);
|
|
|
|
ut_ad(ref_len == dtuple_get_n_fields(ref));
|
|
|
|
for (i = 0; i < ref_len; i++) {
|
|
const dict_col_t* col;
|
|
const dict_field_t* field;
|
|
dfield_t* dfield;
|
|
const dfield_t* dfield2;
|
|
|
|
dfield = dtuple_get_nth_field(ref, i);
|
|
|
|
field = dict_index_get_nth_field(clust_index, i);
|
|
|
|
col = dict_field_get_col(field);
|
|
|
|
dfield2 = dtuple_get_nth_field(row, dict_col_get_no(col));
|
|
|
|
dfield_copy(dfield, dfield2);
|
|
ut_ad(!dfield_is_ext(dfield));
|
|
|
|
if (field->prefix_len > 0 && !dfield_is_null(dfield)) {
|
|
|
|
ulint len = dfield_get_len(dfield);
|
|
|
|
len = dtype_get_at_most_n_mbchars(
|
|
col->prtype, col->mbminlen, col->mbmaxlen,
|
|
field->prefix_len,
|
|
len, dfield_get_data(dfield));
|
|
|
|
dfield_set_len(dfield, len);
|
|
}
|
|
}
|
|
|
|
ut_ad(dtuple_check_typed(ref));
|
|
}
|
|
|
|
/*******************************************************************
|
|
Searches the clustered index record for a row, if we have the row reference. */
|
|
UNIV_INTERN
|
|
ibool
|
|
row_search_on_row_ref(
|
|
/*==================*/
|
|
/* out: TRUE if found */
|
|
btr_pcur_t* pcur, /* out: persistent cursor, which must
|
|
be closed by the caller */
|
|
ulint mode, /* in: BTR_MODIFY_LEAF, ... */
|
|
const dict_table_t* table, /* in: table */
|
|
const dtuple_t* ref, /* in: row reference */
|
|
mtr_t* mtr) /* in/out: mtr */
|
|
{
|
|
ulint low_match;
|
|
rec_t* rec;
|
|
dict_index_t* index;
|
|
|
|
ut_ad(dtuple_check_typed(ref));
|
|
|
|
index = dict_table_get_first_index(table);
|
|
|
|
ut_a(dtuple_get_n_fields(ref) == dict_index_get_n_unique(index));
|
|
|
|
btr_pcur_open(index, ref, PAGE_CUR_LE, mode, pcur, mtr);
|
|
|
|
low_match = btr_pcur_get_low_match(pcur);
|
|
|
|
rec = btr_pcur_get_rec(pcur);
|
|
|
|
if (page_rec_is_infimum(rec)) {
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
if (low_match != dtuple_get_n_fields(ref)) {
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/*************************************************************************
|
|
Fetches the clustered index record for a secondary index record. The latches
|
|
on the secondary index record are preserved. */
|
|
UNIV_INTERN
|
|
rec_t*
|
|
row_get_clust_rec(
|
|
/*==============*/
|
|
/* out: record or NULL, if no record found */
|
|
ulint mode, /* in: BTR_MODIFY_LEAF, ... */
|
|
const rec_t* rec, /* in: record in a secondary index */
|
|
dict_index_t* index, /* in: secondary index */
|
|
dict_index_t** clust_index,/* out: clustered index */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
mem_heap_t* heap;
|
|
dtuple_t* ref;
|
|
dict_table_t* table;
|
|
btr_pcur_t pcur;
|
|
ibool found;
|
|
rec_t* clust_rec;
|
|
|
|
ut_ad(!dict_index_is_clust(index));
|
|
|
|
table = index->table;
|
|
|
|
heap = mem_heap_create(256);
|
|
|
|
ref = row_build_row_ref(ROW_COPY_POINTERS, index, rec, heap);
|
|
|
|
found = row_search_on_row_ref(&pcur, mode, table, ref, mtr);
|
|
|
|
clust_rec = found ? btr_pcur_get_rec(&pcur) : NULL;
|
|
|
|
mem_heap_free(heap);
|
|
|
|
btr_pcur_close(&pcur);
|
|
|
|
*clust_index = dict_table_get_first_index(table);
|
|
|
|
return(clust_rec);
|
|
}
|
|
|
|
/*******************************************************************
|
|
Searches an index record. */
|
|
UNIV_INTERN
|
|
ibool
|
|
row_search_index_entry(
|
|
/*===================*/
|
|
/* out: TRUE if found */
|
|
dict_index_t* index, /* in: index */
|
|
const dtuple_t* entry, /* in: index entry */
|
|
ulint mode, /* in: BTR_MODIFY_LEAF, ... */
|
|
btr_pcur_t* pcur, /* in/out: persistent cursor, which must
|
|
be closed by the caller */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
ulint n_fields;
|
|
ulint low_match;
|
|
rec_t* rec;
|
|
|
|
ut_ad(dtuple_check_typed(entry));
|
|
|
|
btr_pcur_open(index, entry, PAGE_CUR_LE, mode, pcur, mtr);
|
|
low_match = btr_pcur_get_low_match(pcur);
|
|
|
|
rec = btr_pcur_get_rec(pcur);
|
|
|
|
n_fields = dtuple_get_n_fields(entry);
|
|
|
|
return(!page_rec_is_infimum(rec) && low_match == n_fields);
|
|
}
|
|
|
|
#ifndef UNIV_HOTBACKUP
|
|
|
|
#include <my_sys.h>
|
|
|
|
/***********************************************************************
|
|
Formats the raw data in "data" (in InnoDB on-disk format) that is of
|
|
type DATA_INT using "prtype" and writes the result to "buf".
|
|
If the data is in unknown format, then nothing is written to "buf",
|
|
0 is returned and "format_in_hex" is set to TRUE, otherwise
|
|
"format_in_hex" is left untouched.
|
|
Not more than "buf_size" bytes are written to "buf".
|
|
The result is always '\0'-terminated (provided buf_size > 0) and the
|
|
number of bytes that were written to "buf" is returned (including the
|
|
terminating '\0'). */
|
|
static
|
|
ulint
|
|
row_raw_format_int(
|
|
/*===============*/
|
|
/* out: number of bytes
|
|
that were written */
|
|
const char* data, /* in: raw data */
|
|
ulint data_len, /* in: raw data length
|
|
in bytes */
|
|
ulint prtype, /* in: precise type */
|
|
char* buf, /* out: output buffer */
|
|
ulint buf_size, /* in: output buffer size
|
|
in bytes */
|
|
ibool* format_in_hex) /* out: should the data be
|
|
formated in hex */
|
|
{
|
|
ulint ret;
|
|
|
|
if (data_len <= sizeof(ullint)) {
|
|
|
|
ullint value;
|
|
ibool unsigned_type = prtype & DATA_UNSIGNED;
|
|
|
|
value = mach_read_int_type((const byte*) data,
|
|
data_len, unsigned_type);
|
|
|
|
if (unsigned_type) {
|
|
|
|
ret = ut_snprintf(buf, buf_size, "%llu",
|
|
value) + 1;
|
|
} else {
|
|
|
|
ret = ut_snprintf(buf, buf_size, "%lld",
|
|
(long long) value) + 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
*format_in_hex = TRUE;
|
|
ret = 0;
|
|
}
|
|
|
|
return(ut_min(ret, buf_size));
|
|
}
|
|
|
|
/***********************************************************************
|
|
Formats the raw data in "data" (in InnoDB on-disk format) that is of
|
|
type DATA_(CHAR|VARCHAR|MYSQL|VARMYSQL) using "prtype" and writes the
|
|
result to "buf".
|
|
If the data is in binary format, then nothing is written to "buf",
|
|
0 is returned and "format_in_hex" is set to TRUE, otherwise
|
|
"format_in_hex" is left untouched.
|
|
Not more than "buf_size" bytes are written to "buf".
|
|
The result is always '\0'-terminated (provided buf_size > 0) and the
|
|
number of bytes that were written to "buf" is returned (including the
|
|
terminating '\0'). */
|
|
static
|
|
ulint
|
|
row_raw_format_str(
|
|
/*===============*/
|
|
/* out: number of bytes
|
|
that were written */
|
|
const char* data, /* in: raw data */
|
|
ulint data_len, /* in: raw data length
|
|
in bytes */
|
|
ulint prtype, /* in: precise type */
|
|
char* buf, /* out: output buffer */
|
|
ulint buf_size, /* in: output buffer size
|
|
in bytes */
|
|
ibool* format_in_hex) /* out: should the data be
|
|
formated in hex */
|
|
{
|
|
ulint charset_coll;
|
|
|
|
if (buf_size == 0) {
|
|
|
|
return(0);
|
|
}
|
|
|
|
/* we assume system_charset_info is UTF-8 */
|
|
|
|
charset_coll = dtype_get_charset_coll(prtype);
|
|
|
|
if (UNIV_LIKELY(dtype_is_utf8(prtype))) {
|
|
|
|
return(ut_str_sql_format(data, data_len, buf, buf_size));
|
|
}
|
|
/* else */
|
|
|
|
if (charset_coll == DATA_MYSQL_BINARY_CHARSET_COLL) {
|
|
|
|
*format_in_hex = TRUE;
|
|
return(0);
|
|
}
|
|
/* else */
|
|
|
|
return(innobase_raw_format(data, data_len, charset_coll,
|
|
buf, buf_size));
|
|
}
|
|
|
|
/***********************************************************************
|
|
Formats the raw data in "data" (in InnoDB on-disk format) using
|
|
"dict_field" and writes the result to "buf".
|
|
Not more than "buf_size" bytes are written to "buf".
|
|
The result is always '\0'-terminated (provided buf_size > 0) and the
|
|
number of bytes that were written to "buf" is returned (including the
|
|
terminating '\0'). */
|
|
UNIV_INTERN
|
|
ulint
|
|
row_raw_format(
|
|
/*===========*/
|
|
/* out: number of bytes
|
|
that were written */
|
|
const char* data, /* in: raw data */
|
|
ulint data_len, /* in: raw data length
|
|
in bytes */
|
|
const dict_field_t* dict_field, /* in: index field */
|
|
char* buf, /* out: output buffer */
|
|
ulint buf_size) /* in: output buffer size
|
|
in bytes */
|
|
{
|
|
ulint mtype;
|
|
ulint prtype;
|
|
ulint ret;
|
|
ibool format_in_hex;
|
|
|
|
if (buf_size == 0) {
|
|
|
|
return(0);
|
|
}
|
|
|
|
if (data_len == UNIV_SQL_NULL) {
|
|
|
|
ret = ut_snprintf((char*) buf, buf_size, "NULL") + 1;
|
|
|
|
return(ut_min(ret, buf_size));
|
|
}
|
|
|
|
mtype = dict_field->col->mtype;
|
|
prtype = dict_field->col->prtype;
|
|
|
|
format_in_hex = FALSE;
|
|
|
|
switch (mtype) {
|
|
case DATA_INT:
|
|
|
|
ret = row_raw_format_int(data, data_len, prtype,
|
|
buf, buf_size, &format_in_hex);
|
|
break;
|
|
case DATA_CHAR:
|
|
case DATA_VARCHAR:
|
|
case DATA_MYSQL:
|
|
case DATA_VARMYSQL:
|
|
|
|
ret = row_raw_format_str(data, data_len, prtype,
|
|
buf, buf_size, &format_in_hex);
|
|
break;
|
|
/* XXX support more data types */
|
|
default:
|
|
|
|
format_in_hex = TRUE;
|
|
}
|
|
|
|
if (format_in_hex) {
|
|
|
|
if (UNIV_LIKELY(buf_size > 2)) {
|
|
|
|
memcpy(buf, "0x", 2);
|
|
buf += 2;
|
|
buf_size -= 2;
|
|
ret = 2 + ut_raw_to_hex(data, data_len,
|
|
buf, buf_size);
|
|
} else {
|
|
|
|
buf[0] = '\0';
|
|
ret = 1;
|
|
}
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
#endif /* !UNIV_HOTBACKUP */
|
|
|
|
#ifdef UNIV_COMPILE_TEST_FUNCS
|
|
|
|
#include "ut0dbg.h"
|
|
|
|
void
|
|
test_row_raw_format_int()
|
|
{
|
|
ulint ret;
|
|
char buf[128];
|
|
ibool format_in_hex;
|
|
|
|
#define CALL_AND_TEST(data, data_len, prtype, buf, buf_size,\
|
|
ret_expected, buf_expected, format_in_hex_expected)\
|
|
do {\
|
|
ibool ok = TRUE;\
|
|
ulint i;\
|
|
memset(buf, 'x', 10);\
|
|
buf[10] = '\0';\
|
|
format_in_hex = FALSE;\
|
|
fprintf(stderr, "TESTING \"\\x");\
|
|
for (i = 0; i < data_len; i++) {\
|
|
fprintf(stderr, "%02hhX", data[i]);\
|
|
}\
|
|
fprintf(stderr, "\", %lu, %lu, %lu\n",\
|
|
(ulint) data_len, (ulint) prtype,\
|
|
(ulint) buf_size);\
|
|
ret = row_raw_format_int(data, data_len, prtype,\
|
|
buf, buf_size, &format_in_hex);\
|
|
if (ret != ret_expected) {\
|
|
fprintf(stderr, "expected ret %lu, got %lu\n",\
|
|
(ulint) ret_expected, ret);\
|
|
ok = FALSE;\
|
|
}\
|
|
if (strcmp((char*) buf, buf_expected) != 0) {\
|
|
fprintf(stderr, "expected buf \"%s\", got \"%s\"\n",\
|
|
buf_expected, buf);\
|
|
ok = FALSE;\
|
|
}\
|
|
if (format_in_hex != format_in_hex_expected) {\
|
|
fprintf(stderr, "expected format_in_hex %d, got %d\n",\
|
|
(int) format_in_hex_expected,\
|
|
(int) format_in_hex);\
|
|
ok = FALSE;\
|
|
}\
|
|
if (ok) {\
|
|
fprintf(stderr, "OK: %lu, \"%s\" %d\n\n",\
|
|
(ulint) ret, buf, (int) format_in_hex);\
|
|
} else {\
|
|
return;\
|
|
}\
|
|
} while (0)
|
|
|
|
#if 1
|
|
/* min values for signed 1-8 byte integers */
|
|
|
|
CALL_AND_TEST("\x00", 1, 0,
|
|
buf, sizeof(buf), 5, "-128", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00", 2, 0,
|
|
buf, sizeof(buf), 7, "-32768", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00", 3, 0,
|
|
buf, sizeof(buf), 9, "-8388608", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00", 4, 0,
|
|
buf, sizeof(buf), 12, "-2147483648", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00", 5, 0,
|
|
buf, sizeof(buf), 14, "-549755813888", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00\x00", 6, 0,
|
|
buf, sizeof(buf), 17, "-140737488355328", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00", 7, 0,
|
|
buf, sizeof(buf), 19, "-36028797018963968", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00\x00", 8, 0,
|
|
buf, sizeof(buf), 21, "-9223372036854775808", 0);
|
|
|
|
/* min values for unsigned 1-8 byte integers */
|
|
|
|
CALL_AND_TEST("\x00", 1, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00", 2, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00", 3, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00", 4, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00", 5, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00\x00", 6, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00", 7, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00\x00\x00\x00", 8, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 2, "0", 0);
|
|
|
|
/* max values for signed 1-8 byte integers */
|
|
|
|
CALL_AND_TEST("\xFF", 1, 0,
|
|
buf, sizeof(buf), 4, "127", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF", 2, 0,
|
|
buf, sizeof(buf), 6, "32767", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF", 3, 0,
|
|
buf, sizeof(buf), 8, "8388607", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF", 4, 0,
|
|
buf, sizeof(buf), 11, "2147483647", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF", 5, 0,
|
|
buf, sizeof(buf), 13, "549755813887", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF", 6, 0,
|
|
buf, sizeof(buf), 16, "140737488355327", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 7, 0,
|
|
buf, sizeof(buf), 18, "36028797018963967", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8, 0,
|
|
buf, sizeof(buf), 20, "9223372036854775807", 0);
|
|
|
|
/* max values for unsigned 1-8 byte integers */
|
|
|
|
CALL_AND_TEST("\xFF", 1, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 4, "255", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF", 2, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 6, "65535", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF", 3, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 9, "16777215", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF", 4, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 11, "4294967295", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF", 5, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 14, "1099511627775", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF", 6, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 16, "281474976710655", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 7, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 18, "72057594037927935", 0);
|
|
|
|
CALL_AND_TEST("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 21, "18446744073709551615", 0);
|
|
|
|
/* some random values */
|
|
|
|
CALL_AND_TEST("\x52", 1, 0,
|
|
buf, sizeof(buf), 4, "-46", 0);
|
|
|
|
CALL_AND_TEST("\x0E", 1, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 3, "14", 0);
|
|
|
|
CALL_AND_TEST("\x62\xCE", 2, 0,
|
|
buf, sizeof(buf), 6, "-7474", 0);
|
|
|
|
CALL_AND_TEST("\x29\xD6", 2, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 6, "10710", 0);
|
|
|
|
CALL_AND_TEST("\x7F\xFF\x90", 3, 0,
|
|
buf, sizeof(buf), 5, "-112", 0);
|
|
|
|
CALL_AND_TEST("\x00\xA1\x16", 3, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 6, "41238", 0);
|
|
|
|
CALL_AND_TEST("\x7F\xFF\xFF\xF7", 4, 0,
|
|
buf, sizeof(buf), 3, "-9", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x5C", 4, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 3, "92", 0);
|
|
|
|
CALL_AND_TEST("\x7F\xFF\xFF\xFF\xFF\xFF\xDC\x63", 8, 0,
|
|
buf, sizeof(buf), 6, "-9117", 0);
|
|
|
|
CALL_AND_TEST("\x00\x00\x00\x00\x00\x01\x64\x62", 8, DATA_UNSIGNED,
|
|
buf, sizeof(buf), 6, "91234", 0);
|
|
#endif
|
|
|
|
/* speed test */
|
|
|
|
speedo_t speedo;
|
|
ulint i;
|
|
|
|
speedo_reset(&speedo);
|
|
|
|
for (i = 0; i < 1000000; i++) {
|
|
row_raw_format_int("\x23", 1,
|
|
0, buf, sizeof(buf),
|
|
&format_in_hex);
|
|
row_raw_format_int("\x23", 1,
|
|
DATA_UNSIGNED, buf, sizeof(buf),
|
|
&format_in_hex);
|
|
|
|
row_raw_format_int("\x00\x00\x00\x00\x00\x01\x64\x62", 8,
|
|
0, buf, sizeof(buf),
|
|
&format_in_hex);
|
|
row_raw_format_int("\x00\x00\x00\x00\x00\x01\x64\x62", 8,
|
|
DATA_UNSIGNED, buf, sizeof(buf),
|
|
&format_in_hex);
|
|
}
|
|
|
|
speedo_show(&speedo);
|
|
}
|
|
|
|
#endif /* UNIV_COMPILE_TEST_FUNCS */
|