1/*****************************************************************************
2
3Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved.
4Copyright (c) 2017, 2018, MariaDB Corporation.
5
6This program is free software; you can redistribute it and/or modify it under
7the terms of the GNU General Public License as published by the Free Software
8Foundation; version 2 of the License.
9
10This program is distributed in the hope that it will be useful, but WITHOUT
11ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License along with
15this program; if not, write to the Free Software Foundation, Inc.,
1651 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
17
18*****************************************************************************/
19
20/********************************************************************//**
21@file rem/rem0rec.cc
22Record manager
23
24Created 5/30/1994 Heikki Tuuri
25*************************************************************************/
26
27#include "rem0rec.h"
28#include "page0page.h"
29#include "mtr0mtr.h"
30#include "mtr0log.h"
31#include "fts0fts.h"
32#ifdef WITH_WSREP
33#include <ha_prototypes.h>
34#endif /* WITH_WSREP */
35#include "gis0geo.h"
36#include "trx0sys.h"
37#include "mach0data.h"
38
39/* PHYSICAL RECORD (OLD STYLE)
40 ===========================
41
42The physical record, which is the data type of all the records
43found in index pages of the database, has the following format
44(lower addresses and more significant bits inside a byte are below
45represented on a higher text line):
46
47| offset of the end of the last field of data, the most significant
48 bit is set to 1 if and only if the field is SQL-null,
49 if the offset is 2-byte, then the second most significant
50 bit is set to 1 if the field is stored on another page:
51 mostly this will occur in the case of big BLOB fields |
52...
53| offset of the end of the first field of data + the SQL-null bit |
54| 4 bits used to delete mark a record, and mark a predefined
55 minimum record in alphabetical order |
56| 4 bits giving the number of records owned by this record
57 (this term is explained in page0page.h) |
58| 13 bits giving the order number of this record in the
59 heap of the index page |
60| 10 bits giving the number of fields in this record |
61| 1 bit which is set to 1 if the offsets above are given in
62 one byte format, 0 if in two byte format |
63| two bytes giving an absolute pointer to the next record in the page |
64ORIGIN of the record
65| first field of data |
66...
67| last field of data |
68
69The origin of the record is the start address of the first field
70of data. The offsets are given relative to the origin.
71The offsets of the data fields are stored in an inverted
72order because then the offset of the first fields are near the
73origin, giving maybe a better processor cache hit rate in searches.
74
75The offsets of the data fields are given as one-byte
76(if there are less than 127 bytes of data in the record)
77or two-byte unsigned integers. The most significant bit
78is not part of the offset, instead it indicates the SQL-null
79if the bit is set to 1. */
80
81/* PHYSICAL RECORD (NEW STYLE)
82 ===========================
83
84The physical record, which is the data type of all the records
85found in index pages of the database, has the following format
86(lower addresses and more significant bits inside a byte are below
87represented on a higher text line):
88
89| length of the last non-null variable-length field of data:
90 if the maximum length is 255, one byte; otherwise,
91 0xxxxxxx (one byte, length=0..127), or 1exxxxxxxxxxxxxx (two bytes,
92 length=128..16383, extern storage flag) |
93...
94| length of first variable-length field of data |
95| SQL-null flags (1 bit per nullable field), padded to full bytes |
96| 4 bits used to delete mark a record, and mark a predefined
97 minimum record in alphabetical order |
98| 4 bits giving the number of records owned by this record
99 (this term is explained in page0page.h) |
100| 13 bits giving the order number of this record in the
101 heap of the index page |
102| 3 bits record type: 000=conventional, 001=node pointer (inside B-tree),
103 010=infimum, 011=supremum, 1xx=reserved |
104| two bytes giving a relative pointer to the next record in the page |
105ORIGIN of the record
106| first field of data |
107...
108| last field of data |
109
110The origin of the record is the start address of the first field
111of data. The offsets are given relative to the origin.
112The offsets of the data fields are stored in an inverted
113order because then the offset of the first fields are near the
114origin, giving maybe a better processor cache hit rate in searches.
115
116The offsets of the data fields are given as one-byte
117(if there are less than 127 bytes of data in the record)
118or two-byte unsigned integers. The most significant bit
119is not part of the offset, instead it indicates the SQL-null
120if the bit is set to 1. */
121
122/* CANONICAL COORDINATES. A record can be seen as a single
123string of 'characters' in the following way: catenate the bytes
124in each field, in the order of fields. An SQL-null field
125is taken to be an empty sequence of bytes. Then after
126the position of each field insert in the string
127the 'character' <FIELD-END>, except that after an SQL-null field
128insert <NULL-FIELD-END>. Now the ordinal position of each
129byte in this canonical string is its canonical coordinate.
130So, for the record ("AA", SQL-NULL, "BB", ""), the canonical
131string is "AA<FIELD_END><NULL-FIELD-END>BB<FIELD-END><FIELD-END>".
132We identify prefixes (= initial segments) of a record
133with prefixes of the canonical string. The canonical
134length of the prefix is the length of the corresponding
135prefix of the canonical string. The canonical length of
136a record is the length of its canonical string.
137
138For example, the maximal common prefix of records
139("AA", SQL-NULL, "BB", "C") and ("AA", SQL-NULL, "B", "C")
140is "AA<FIELD-END><NULL-FIELD-END>B", and its canonical
141length is 5.
142
143A complete-field prefix of a record is a prefix which ends at the
144end of some field (containing also <FIELD-END>).
145A record is a complete-field prefix of another record, if
146the corresponding canonical strings have the same property. */
147
148/***************************************************************//**
149Validates the consistency of an old-style physical record.
150@return TRUE if ok */
151static
152ibool
153rec_validate_old(
154/*=============*/
155 const rec_t* rec); /*!< in: physical record */
156
157/******************************************************//**
158Determine how many of the first n columns in a compact
159physical record are stored externally.
160@return number of externally stored columns */
161ulint
162rec_get_n_extern_new(
163/*=================*/
164 const rec_t* rec, /*!< in: compact physical record */
165 const dict_index_t* index, /*!< in: record descriptor */
166 ulint n) /*!< in: number of columns to scan */
167{
168 const byte* nulls;
169 const byte* lens;
170 ulint null_mask;
171 ulint n_extern;
172 ulint i;
173
174 ut_ad(dict_table_is_comp(index->table));
175 ut_ad(!index->table->supports_instant() || index->is_dummy);
176 ut_ad(!index->is_instant());
177 ut_ad(rec_get_status(rec) == REC_STATUS_ORDINARY
178 || rec_get_status(rec) == REC_STATUS_COLUMNS_ADDED);
179 ut_ad(n == ULINT_UNDEFINED || n <= dict_index_get_n_fields(index));
180
181 if (n == ULINT_UNDEFINED) {
182 n = dict_index_get_n_fields(index);
183 }
184
185 nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
186 lens = nulls - UT_BITS_IN_BYTES(index->n_nullable);
187 null_mask = 1;
188 n_extern = 0;
189 i = 0;
190
191 /* read the lengths of fields 0..n */
192 do {
193 const dict_field_t* field
194 = dict_index_get_nth_field(index, i);
195 const dict_col_t* col
196 = dict_field_get_col(field);
197 ulint len;
198
199 if (!(col->prtype & DATA_NOT_NULL)) {
200 /* nullable field => read the null flag */
201
202 if (UNIV_UNLIKELY(!(byte) null_mask)) {
203 nulls--;
204 null_mask = 1;
205 }
206
207 if (*nulls & null_mask) {
208 null_mask <<= 1;
209 /* No length is stored for NULL fields. */
210 continue;
211 }
212 null_mask <<= 1;
213 }
214
215 if (UNIV_UNLIKELY(!field->fixed_len)) {
216 /* Variable-length field: read the length */
217 len = *lens--;
218 /* If the maximum length of the field is up
219 to 255 bytes, the actual length is always
220 stored in one byte. If the maximum length is
221 more than 255 bytes, the actual length is
222 stored in one byte for 0..127. The length
223 will be encoded in two bytes when it is 128 or
224 more, or when the field is stored externally. */
225 if (DATA_BIG_COL(col)) {
226 if (len & 0x80) {
227 /* 1exxxxxxx xxxxxxxx */
228 if (len & 0x40) {
229 n_extern++;
230 }
231 lens--;
232 }
233 }
234 }
235 } while (++i < n);
236
237 return(n_extern);
238}
239
240/** Get the length of added field count in a REC_STATUS_COLUMNS_ADDED record.
241@param[in] n_add_field number of added fields, minus one
242@return storage size of the field count, in bytes */
243static inline unsigned rec_get_n_add_field_len(ulint n_add_field)
244{
245 ut_ad(n_add_field < REC_MAX_N_FIELDS);
246 return n_add_field < 0x80 ? 1 : 2;
247}
248
249/** Get the added field count in a REC_STATUS_COLUMNS_ADDED record.
250@param[in,out] header variable header of a REC_STATUS_COLUMNS_ADDED record
251@return number of added fields */
252static inline unsigned rec_get_n_add_field(const byte*& header)
253{
254 unsigned n_fields_add = *--header;
255 if (n_fields_add < 0x80) {
256 ut_ad(rec_get_n_add_field_len(n_fields_add) == 1);
257 return n_fields_add;
258 }
259
260 n_fields_add &= 0x7f;
261 n_fields_add |= unsigned(*--header) << 7;
262 ut_ad(n_fields_add < REC_MAX_N_FIELDS);
263 ut_ad(rec_get_n_add_field_len(n_fields_add) == 2);
264 return n_fields_add;
265}
266
267/** Set the added field count in a REC_STATUS_COLUMNS_ADDED record.
268@param[in,out] header variable header of a REC_STATUS_COLUMNS_ADDED record
269@param[in] n_add number of added fields, minus 1
270@return record header before the number of added fields */
271static inline void rec_set_n_add_field(byte*& header, ulint n_add)
272{
273 ut_ad(n_add < REC_MAX_N_FIELDS);
274
275 if (n_add < 0x80) {
276 *header-- = byte(n_add);
277 } else {
278 *header-- = byte(n_add) | 0x80;
279 *header-- = byte(n_add >> 7);
280 }
281}
282
283/** Format of a leaf-page ROW_FORMAT!=REDUNDANT record */
284enum rec_leaf_format {
285 /** Temporary file record */
286 REC_LEAF_TEMP,
287 /** Temporary file record, with added columns
288 (REC_STATUS_COLUMNS_ADDED) */
289 REC_LEAF_TEMP_COLUMNS_ADDED,
290 /** Normal (REC_STATUS_ORDINARY) */
291 REC_LEAF_ORDINARY,
292 /** With added columns (REC_STATUS_COLUMNS_ADDED) */
293 REC_LEAF_COLUMNS_ADDED
294};
295
296/** Determine the offset to each field in a leaf-page record
297in ROW_FORMAT=COMPACT,DYNAMIC,COMPRESSED.
298This is a special case of rec_init_offsets() and rec_get_offsets_func().
299@param[in] rec leaf-page record
300@param[in] index the index that the record belongs in
301@param[in] n_core number of core fields (index->n_core_fields)
302@param[in,out] offsets offsets, with valid rec_offs_n_fields(offsets)
303@param[in] format record format */
304static inline
305void
306rec_init_offsets_comp_ordinary(
307 const rec_t* rec,
308 const dict_index_t* index,
309 ulint* offsets,
310 ulint n_core,
311 rec_leaf_format format)
312{
313 ulint offs = 0;
314 ulint any = 0;
315 const byte* nulls = rec;
316 const byte* lens = NULL;
317 ulint n_fields = n_core;
318 ulint null_mask = 1;
319
320 ut_ad(index->n_core_fields >= n_core);
321 ut_ad(n_core > 0);
322 ut_ad(index->n_fields >= n_core);
323 ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable));
324 ut_ad(format == REC_LEAF_TEMP || format == REC_LEAF_TEMP_COLUMNS_ADDED
325 || dict_table_is_comp(index->table));
326 ut_ad(format != REC_LEAF_TEMP_COLUMNS_ADDED
327 || index->n_fields == rec_offs_n_fields(offsets));
328 ut_d(ulint n_null= 0);
329
330 switch (format) {
331 case REC_LEAF_TEMP:
332 if (dict_table_is_comp(index->table)) {
333 /* No need to do adjust fixed_len=0. We only need to
334 adjust it for ROW_FORMAT=REDUNDANT. */
335 format = REC_LEAF_ORDINARY;
336 }
337 goto ordinary;
338 case REC_LEAF_ORDINARY:
339 nulls -= REC_N_NEW_EXTRA_BYTES;
340ordinary:
341 lens = --nulls - index->n_core_null_bytes;
342
343 ut_d(n_null = std::min(index->n_core_null_bytes * 8U,
344 index->n_nullable));
345 break;
346 case REC_LEAF_COLUMNS_ADDED:
347 /* We would have !index->is_instant() when rolling back
348 an instant ADD COLUMN operation. */
349 nulls -= REC_N_NEW_EXTRA_BYTES;
350 ut_ad(index->is_instant());
351 /* fall through */
352 case REC_LEAF_TEMP_COLUMNS_ADDED:
353 n_fields = n_core + 1 + rec_get_n_add_field(nulls);
354 ut_ad(n_fields <= index->n_fields);
355 const ulint n_nullable = index->get_n_nullable(n_fields);
356 const ulint n_null_bytes = UT_BITS_IN_BYTES(n_nullable);
357 ut_d(n_null = n_nullable);
358 ut_ad(n_null <= index->n_nullable);
359 ut_ad(n_null_bytes >= index->n_core_null_bytes
360 || n_core < index->n_core_fields);
361 lens = --nulls - n_null_bytes;
362 }
363
364#ifdef UNIV_DEBUG
365 /* We cannot invoke rec_offs_make_valid() if format==REC_LEAF_TEMP.
366 Similarly, rec_offs_validate() will fail in that case, because
367 it invokes rec_get_status(). */
368 offsets[2] = (ulint) rec;
369 offsets[3] = (ulint) index;
370#endif /* UNIV_DEBUG */
371
372 /* read the lengths of fields 0..n_fields */
373 ulint i = 0;
374 do {
375 const dict_field_t* field
376 = dict_index_get_nth_field(index, i);
377 const dict_col_t* col
378 = dict_field_get_col(field);
379 ulint len;
380
381 /* set default value flag */
382 if (i >= n_fields) {
383 ulint dlen;
384 if (!index->instant_field_value(i, &dlen)) {
385 len = offs | REC_OFFS_SQL_NULL;
386 ut_ad(dlen == UNIV_SQL_NULL);
387 } else {
388 len = offs | REC_OFFS_DEFAULT;
389 any |= REC_OFFS_DEFAULT;
390 }
391
392 goto resolved;
393 }
394
395 if (!(col->prtype & DATA_NOT_NULL)) {
396 /* nullable field => read the null flag */
397 ut_ad(n_null--);
398
399 if (UNIV_UNLIKELY(!(byte) null_mask)) {
400 nulls--;
401 null_mask = 1;
402 }
403
404 if (*nulls & null_mask) {
405 null_mask <<= 1;
406 /* No length is stored for NULL fields.
407 We do not advance offs, and we set
408 the length to zero and enable the
409 SQL NULL flag in offsets[]. */
410 len = offs | REC_OFFS_SQL_NULL;
411 goto resolved;
412 }
413 null_mask <<= 1;
414 }
415
416 if (!field->fixed_len
417 || (format == REC_LEAF_TEMP
418 && !dict_col_get_fixed_size(col, true))) {
419 /* Variable-length field: read the length */
420 len = *lens--;
421 /* If the maximum length of the field is up
422 to 255 bytes, the actual length is always
423 stored in one byte. If the maximum length is
424 more than 255 bytes, the actual length is
425 stored in one byte for 0..127. The length
426 will be encoded in two bytes when it is 128 or
427 more, or when the field is stored externally. */
428 if ((len & 0x80) && DATA_BIG_COL(col)) {
429 /* 1exxxxxxx xxxxxxxx */
430 len <<= 8;
431 len |= *lens--;
432
433 offs += len & 0x3fff;
434 if (UNIV_UNLIKELY(len & 0x4000)) {
435 ut_ad(dict_index_is_clust(index));
436 any |= REC_OFFS_EXTERNAL;
437 len = offs | REC_OFFS_EXTERNAL;
438 } else {
439 len = offs;
440 }
441
442 goto resolved;
443 }
444
445 len = offs += len;
446 } else {
447 len = offs += field->fixed_len;
448 }
449resolved:
450 rec_offs_base(offsets)[i + 1] = len;
451 } while (++i < rec_offs_n_fields(offsets));
452
453 *rec_offs_base(offsets)
454 = ulint(rec - (lens + 1)) | REC_OFFS_COMPACT | any;
455}
456
457#ifdef UNIV_DEBUG
458/** Update debug data in offsets, in order to tame rec_offs_validate().
459@param[in] rec record
460@param[in] index the index that the record belongs in
461@param[in] leaf whether the record resides in a leaf page
462@param[in,out] offsets offsets from rec_get_offsets() to adjust */
463void
464rec_offs_make_valid(
465 const rec_t* rec,
466 const dict_index_t* index,
467 bool leaf,
468 ulint* offsets)
469{
470 ut_ad(rec_offs_n_fields(offsets)
471 <= (leaf
472 ? dict_index_get_n_fields(index)
473 : dict_index_get_n_unique_in_tree_nonleaf(index) + 1)
474 || index->is_dummy || dict_index_is_ibuf(index));
475 const bool is_user_rec = (dict_table_is_comp(index->table)
476 ? rec_get_heap_no_new(rec)
477 : rec_get_heap_no_old(rec))
478 >= PAGE_HEAP_NO_USER_LOW;
479 ulint n = rec_get_n_fields(rec, index);
480 /* The infimum and supremum records carry 1 field. */
481 ut_ad(is_user_rec || n == 1);
482 ut_ad(is_user_rec || rec_offs_n_fields(offsets) == 1);
483 ut_ad(!is_user_rec
484 || (n + (index->id == DICT_INDEXES_ID)) >= index->n_core_fields
485 || n >= rec_offs_n_fields(offsets));
486 for (; n < rec_offs_n_fields(offsets); n++) {
487 ut_ad(leaf);
488 ut_ad(rec_offs_base(offsets)[1 + n] & REC_OFFS_DEFAULT);
489 }
490 offsets[2] = ulint(rec);
491 offsets[3] = ulint(index);
492}
493
494/** Validate offsets returned by rec_get_offsets().
495@param[in] rec record, or NULL
496@param[in] index the index that the record belongs in, or NULL
497@param[in,out] offsets the offsets of the record
498@return true */
499bool
500rec_offs_validate(
501 const rec_t* rec,
502 const dict_index_t* index,
503 const ulint* offsets)
504{
505 ulint i = rec_offs_n_fields(offsets);
506 ulint last = ULINT_MAX;
507 ulint comp = *rec_offs_base(offsets) & REC_OFFS_COMPACT;
508
509 if (rec) {
510 ut_ad(ulint(rec) == offsets[2]);
511 if (!comp) {
512 const bool is_user_rec = rec_get_heap_no_old(rec)
513 >= PAGE_HEAP_NO_USER_LOW;
514 ulint n = rec_get_n_fields_old(rec);
515 /* The infimum and supremum records carry 1 field. */
516 ut_ad(is_user_rec || n == 1);
517 ut_ad(is_user_rec || i == 1);
518 ut_ad(!is_user_rec || n >= i || !index
519 || (n + (index->id == DICT_INDEXES_ID))
520 >= index->n_core_fields);
521 for (; n < i; n++) {
522 ut_ad(rec_offs_base(offsets)[1 + n]
523 & REC_OFFS_DEFAULT);
524 }
525 }
526 }
527 if (index) {
528 ulint max_n_fields;
529 ut_ad(ulint(index) == offsets[3]);
530 max_n_fields = ut_max(
531 dict_index_get_n_fields(index),
532 dict_index_get_n_unique_in_tree(index) + 1);
533 if (comp && rec) {
534 switch (rec_get_status(rec)) {
535 case REC_STATUS_COLUMNS_ADDED:
536 case REC_STATUS_ORDINARY:
537 break;
538 case REC_STATUS_NODE_PTR:
539 max_n_fields = dict_index_get_n_unique_in_tree(
540 index) + 1;
541 break;
542 case REC_STATUS_INFIMUM:
543 case REC_STATUS_SUPREMUM:
544 max_n_fields = 1;
545 break;
546 default:
547 ut_error;
548 }
549 }
550 /* index->n_def == 0 for dummy indexes if !comp */
551 ut_a(!comp || index->n_def);
552 ut_a(!index->n_def || i <= max_n_fields);
553 }
554 while (i--) {
555 ulint curr = rec_offs_base(offsets)[1 + i] & REC_OFFS_MASK;
556 ut_a(curr <= last);
557 last = curr;
558 }
559 return(TRUE);
560}
561#endif /* UNIV_DEBUG */
562
563/** Determine the offsets to each field in the record.
564 The offsets are written to a previously allocated array of
565ulint, where rec_offs_n_fields(offsets) has been initialized to the
566number of fields in the record. The rest of the array will be
567initialized by this function. rec_offs_base(offsets)[0] will be set
568to the extra size (if REC_OFFS_COMPACT is set, the record is in the
569new format; if REC_OFFS_EXTERNAL is set, the record contains externally
570stored columns), and rec_offs_base(offsets)[1..n_fields] will be set to
571offsets past the end of fields 0..n_fields, or to the beginning of
572fields 1..n_fields+1. When the high-order bit of the offset at [i+1]
573is set (REC_OFFS_SQL_NULL), the field i is NULL. When the second
574high-order bit of the offset at [i+1] is set (REC_OFFS_EXTERNAL), the
575field i is being stored externally.
576@param[in] rec record
577@param[in] index the index that the record belongs in
578@param[in] leaf whether the record resides in a leaf page
579@param[in,out] offsets array of offsets, with valid rec_offs_n_fields() */
580static
581void
582rec_init_offsets(
583 const rec_t* rec,
584 const dict_index_t* index,
585 bool leaf,
586 ulint* offsets)
587{
588 ulint i = 0;
589 ulint offs;
590
591 ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable));
592 ut_d(offsets[2] = ulint(rec));
593 ut_d(offsets[3] = ulint(index));
594
595 if (dict_table_is_comp(index->table)) {
596 const byte* nulls;
597 const byte* lens;
598 dict_field_t* field;
599 ulint null_mask;
600 rec_comp_status_t status = rec_get_status(rec);
601 ulint n_node_ptr_field = ULINT_UNDEFINED;
602
603 switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) {
604 case REC_STATUS_INFIMUM:
605 case REC_STATUS_SUPREMUM:
606 /* the field is 8 bytes long */
607 rec_offs_base(offsets)[0]
608 = REC_N_NEW_EXTRA_BYTES | REC_OFFS_COMPACT;
609 rec_offs_base(offsets)[1] = 8;
610 return;
611 case REC_STATUS_NODE_PTR:
612 ut_ad(!leaf);
613 n_node_ptr_field
614 = dict_index_get_n_unique_in_tree_nonleaf(
615 index);
616 break;
617 case REC_STATUS_COLUMNS_ADDED:
618 ut_ad(leaf);
619 rec_init_offsets_comp_ordinary(rec, index, offsets,
620 index->n_core_fields,
621 REC_LEAF_COLUMNS_ADDED);
622 return;
623 case REC_STATUS_ORDINARY:
624 ut_ad(leaf);
625 rec_init_offsets_comp_ordinary(rec, index, offsets,
626 index->n_core_fields,
627 REC_LEAF_ORDINARY);
628 return;
629 }
630
631 /* The n_nullable flags in the clustered index node pointer
632 records in ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC must
633 reflect the number of 'core columns'. These flags are
634 useless garbage, and they are only reserved because of
635 file format compatibility.
636 (Clustered index node pointer records only contain the
637 PRIMARY KEY columns, which are always NOT NULL,
638 so we should have used n_nullable=0.) */
639 ut_ad(index->n_core_fields > 0);
640
641 nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
642 lens = nulls - index->n_core_null_bytes;
643 offs = 0;
644 null_mask = 1;
645
646 /* read the lengths of fields 0..n */
647 do {
648 ulint len;
649 if (UNIV_UNLIKELY(i == n_node_ptr_field)) {
650 len = offs += REC_NODE_PTR_SIZE;
651 goto resolved;
652 }
653
654 field = dict_index_get_nth_field(index, i);
655 if (!(dict_field_get_col(field)->prtype
656 & DATA_NOT_NULL)) {
657 /* nullable field => read the null flag */
658
659 if (UNIV_UNLIKELY(!(byte) null_mask)) {
660 nulls--;
661 null_mask = 1;
662 }
663
664 if (*nulls & null_mask) {
665 null_mask <<= 1;
666 /* No length is stored for NULL fields.
667 We do not advance offs, and we set
668 the length to zero and enable the
669 SQL NULL flag in offsets[]. */
670 len = offs | REC_OFFS_SQL_NULL;
671 goto resolved;
672 }
673 null_mask <<= 1;
674 }
675
676 if (UNIV_UNLIKELY(!field->fixed_len)) {
677 const dict_col_t* col
678 = dict_field_get_col(field);
679 /* Variable-length field: read the length */
680 len = *lens--;
681 /* If the maximum length of the field
682 is up to 255 bytes, the actual length
683 is always stored in one byte. If the
684 maximum length is more than 255 bytes,
685 the actual length is stored in one
686 byte for 0..127. The length will be
687 encoded in two bytes when it is 128 or
688 more, or when the field is stored
689 externally. */
690 if (DATA_BIG_COL(col)) {
691 if (len & 0x80) {
692 /* 1exxxxxxx xxxxxxxx */
693
694 len <<= 8;
695 len |= *lens--;
696
697 /* B-tree node pointers
698 must not contain externally
699 stored columns. Thus
700 the "e" flag must be 0. */
701 ut_a(!(len & 0x4000));
702 offs += len & 0x3fff;
703 len = offs;
704
705 goto resolved;
706 }
707 }
708
709 len = offs += len;
710 } else {
711 len = offs += field->fixed_len;
712 }
713resolved:
714 rec_offs_base(offsets)[i + 1] = len;
715 } while (++i < rec_offs_n_fields(offsets));
716
717 *rec_offs_base(offsets)
718 = ulint(rec - (lens + 1)) | REC_OFFS_COMPACT;
719 } else {
720 /* Old-style record: determine extra size and end offsets */
721 offs = REC_N_OLD_EXTRA_BYTES;
722 const ulint n_fields = rec_get_n_fields_old(rec);
723 const ulint n = std::min(n_fields, rec_offs_n_fields(offsets));
724 ulint any;
725
726 if (rec_get_1byte_offs_flag(rec)) {
727 offs += n_fields;
728 any = offs;
729 /* Determine offsets to fields */
730 do {
731 offs = rec_1_get_field_end_info(rec, i);
732 if (offs & REC_1BYTE_SQL_NULL_MASK) {
733 offs &= ~REC_1BYTE_SQL_NULL_MASK;
734 offs |= REC_OFFS_SQL_NULL;
735 }
736 rec_offs_base(offsets)[1 + i] = offs;
737 } while (++i < n);
738 } else {
739 offs += 2 * n_fields;
740 any = offs;
741 /* Determine offsets to fields */
742 do {
743 offs = rec_2_get_field_end_info(rec, i);
744 if (offs & REC_2BYTE_SQL_NULL_MASK) {
745 offs &= ~REC_2BYTE_SQL_NULL_MASK;
746 offs |= REC_OFFS_SQL_NULL;
747 }
748 if (offs & REC_2BYTE_EXTERN_MASK) {
749 offs &= ~REC_2BYTE_EXTERN_MASK;
750 offs |= REC_OFFS_EXTERNAL;
751 any |= REC_OFFS_EXTERNAL;
752 }
753 rec_offs_base(offsets)[1 + i] = offs;
754 } while (++i < n);
755 }
756
757 if (i < rec_offs_n_fields(offsets)) {
758 offs = (rec_offs_base(offsets)[i] & REC_OFFS_MASK)
759 | REC_OFFS_DEFAULT;
760
761 do {
762 rec_offs_base(offsets)[1 + i] = offs;
763 } while (++i < rec_offs_n_fields(offsets));
764
765 any |= REC_OFFS_DEFAULT;
766 }
767
768 *rec_offs_base(offsets) = any;
769 }
770}
771
772/** Determine the offsets to each field in an index record.
773@param[in] rec physical record
774@param[in] index the index that the record belongs to
775@param[in,out] offsets array comprising offsets[0] allocated elements,
776 or an array from rec_get_offsets(), or NULL
777@param[in] leaf whether this is a leaf-page record
778@param[in] n_fields maximum number of offsets to compute
779 (ULINT_UNDEFINED to compute all offsets)
780@param[in,out] heap memory heap
781@return the new offsets */
782ulint*
783rec_get_offsets_func(
784 const rec_t* rec,
785 const dict_index_t* index,
786 ulint* offsets,
787 bool leaf,
788 ulint n_fields,
789#ifdef UNIV_DEBUG
790 const char* file, /*!< in: file name where called */
791 unsigned line, /*!< in: line number where called */
792#endif /* UNIV_DEBUG */
793 mem_heap_t** heap) /*!< in/out: memory heap */
794{
795 ulint n;
796 ulint size;
797
798 ut_ad(rec);
799 ut_ad(index);
800 ut_ad(heap);
801
802 if (dict_table_is_comp(index->table)) {
803 switch (UNIV_EXPECT(rec_get_status(rec),
804 REC_STATUS_ORDINARY)) {
805 case REC_STATUS_COLUMNS_ADDED:
806 case REC_STATUS_ORDINARY:
807 ut_ad(leaf);
808 n = dict_index_get_n_fields(index);
809 break;
810 case REC_STATUS_NODE_PTR:
811 /* Node pointer records consist of the
812 uniquely identifying fields of the record
813 followed by a child page number field. */
814 ut_ad(!leaf);
815 n = dict_index_get_n_unique_in_tree_nonleaf(index) + 1;
816 break;
817 case REC_STATUS_INFIMUM:
818 case REC_STATUS_SUPREMUM:
819 /* infimum or supremum record */
820 ut_ad(rec_get_heap_no_new(rec)
821 == ulint(rec_get_status(rec)
822 == REC_STATUS_INFIMUM
823 ? PAGE_HEAP_NO_INFIMUM
824 : PAGE_HEAP_NO_SUPREMUM));
825 n = 1;
826 break;
827 default:
828 ut_error;
829 return(NULL);
830 }
831 } else {
832 n = rec_get_n_fields_old(rec);
833 /* Here, rec can be allocated from the heap (copied
834 from an index page record), or it can be located in an
835 index page. If rec is not in an index page, then
836 page_rec_is_user_rec(rec) and similar predicates
837 cannot be evaluated. We can still distinguish the
838 infimum and supremum record based on the heap number. */
839 const bool is_user_rec = rec_get_heap_no_old(rec)
840 >= PAGE_HEAP_NO_USER_LOW;
841 /* The infimum and supremum records carry 1 field. */
842 ut_ad(is_user_rec || n == 1);
843 ut_ad(!is_user_rec || leaf || index->is_dummy
844 || dict_index_is_ibuf(index)
845 || n == n_fields /* dict_stats_analyze_index_level() */
846 || n
847 == dict_index_get_n_unique_in_tree_nonleaf(index) + 1);
848 ut_ad(!is_user_rec || !leaf || index->is_dummy
849 || dict_index_is_ibuf(index)
850 || n == n_fields /* btr_pcur_restore_position() */
851 || (n + (index->id == DICT_INDEXES_ID)
852 >= index->n_core_fields && n <= index->n_fields));
853
854 if (is_user_rec && leaf && n < index->n_fields) {
855 ut_ad(!index->is_dummy);
856 ut_ad(!dict_index_is_ibuf(index));
857 n = index->n_fields;
858 }
859 }
860
861 if (UNIV_UNLIKELY(n_fields < n)) {
862 n = n_fields;
863 }
864
865 /* The offsets header consists of the allocation size at
866 offsets[0] and the REC_OFFS_HEADER_SIZE bytes. */
867 size = n + (1 + REC_OFFS_HEADER_SIZE);
868
869 if (UNIV_UNLIKELY(!offsets)
870 || UNIV_UNLIKELY(rec_offs_get_n_alloc(offsets) < size)) {
871 if (UNIV_UNLIKELY(!*heap)) {
872 *heap = mem_heap_create_at(size * sizeof(ulint),
873 file, line);
874 }
875 offsets = static_cast<ulint*>(
876 mem_heap_alloc(*heap, size * sizeof(ulint)));
877
878 rec_offs_set_n_alloc(offsets, size);
879 }
880
881 rec_offs_set_n_fields(offsets, n);
882 rec_init_offsets(rec, index, leaf, offsets);
883 return(offsets);
884}
885
886/******************************************************//**
887The following function determines the offsets to each field
888in the record. It can reuse a previously allocated array. */
889void
890rec_get_offsets_reverse(
891/*====================*/
892 const byte* extra, /*!< in: the extra bytes of a
893 compact record in reverse order,
894 excluding the fixed-size
895 REC_N_NEW_EXTRA_BYTES */
896 const dict_index_t* index, /*!< in: record descriptor */
897 ulint node_ptr,/*!< in: nonzero=node pointer,
898 0=leaf node */
899 ulint* offsets)/*!< in/out: array consisting of
900 offsets[0] allocated elements */
901{
902 ulint n;
903 ulint i;
904 ulint offs;
905 ulint any_ext;
906 const byte* nulls;
907 const byte* lens;
908 dict_field_t* field;
909 ulint null_mask;
910 ulint n_node_ptr_field;
911
912 ut_ad(extra);
913 ut_ad(index);
914 ut_ad(offsets);
915 ut_ad(dict_table_is_comp(index->table));
916 ut_ad(!index->is_instant());
917
918 if (UNIV_UNLIKELY(node_ptr != 0)) {
919 n_node_ptr_field =
920 dict_index_get_n_unique_in_tree_nonleaf(index);
921 n = n_node_ptr_field + 1;
922 } else {
923 n_node_ptr_field = ULINT_UNDEFINED;
924 n = dict_index_get_n_fields(index);
925 }
926
927 ut_a(rec_offs_get_n_alloc(offsets) >= n + (1 + REC_OFFS_HEADER_SIZE));
928 rec_offs_set_n_fields(offsets, n);
929
930 nulls = extra;
931 lens = nulls + UT_BITS_IN_BYTES(index->n_nullable);
932 i = offs = 0;
933 null_mask = 1;
934 any_ext = 0;
935
936 /* read the lengths of fields 0..n */
937 do {
938 ulint len;
939 if (UNIV_UNLIKELY(i == n_node_ptr_field)) {
940 len = offs += REC_NODE_PTR_SIZE;
941 goto resolved;
942 }
943
944 field = dict_index_get_nth_field(index, i);
945 if (!(dict_field_get_col(field)->prtype & DATA_NOT_NULL)) {
946 /* nullable field => read the null flag */
947
948 if (UNIV_UNLIKELY(!(byte) null_mask)) {
949 nulls++;
950 null_mask = 1;
951 }
952
953 if (*nulls & null_mask) {
954 null_mask <<= 1;
955 /* No length is stored for NULL fields.
956 We do not advance offs, and we set
957 the length to zero and enable the
958 SQL NULL flag in offsets[]. */
959 len = offs | REC_OFFS_SQL_NULL;
960 goto resolved;
961 }
962 null_mask <<= 1;
963 }
964
965 if (UNIV_UNLIKELY(!field->fixed_len)) {
966 /* Variable-length field: read the length */
967 const dict_col_t* col
968 = dict_field_get_col(field);
969 len = *lens++;
970 /* If the maximum length of the field is up
971 to 255 bytes, the actual length is always
972 stored in one byte. If the maximum length is
973 more than 255 bytes, the actual length is
974 stored in one byte for 0..127. The length
975 will be encoded in two bytes when it is 128 or
976 more, or when the field is stored externally. */
977 if (DATA_BIG_COL(col)) {
978 if (len & 0x80) {
979 /* 1exxxxxxx xxxxxxxx */
980 len <<= 8;
981 len |= *lens++;
982
983 offs += len & 0x3fff;
984 if (UNIV_UNLIKELY(len & 0x4000)) {
985 any_ext = REC_OFFS_EXTERNAL;
986 len = offs | REC_OFFS_EXTERNAL;
987 } else {
988 len = offs;
989 }
990
991 goto resolved;
992 }
993 }
994
995 len = offs += len;
996 } else {
997 len = offs += field->fixed_len;
998 }
999resolved:
1000 rec_offs_base(offsets)[i + 1] = len;
1001 } while (++i < rec_offs_n_fields(offsets));
1002
1003 ut_ad(lens >= extra);
1004 *rec_offs_base(offsets) = (ulint(lens - extra) + REC_N_NEW_EXTRA_BYTES)
1005 | REC_OFFS_COMPACT | any_ext;
1006}
1007
1008/************************************************************//**
1009The following function is used to get the offset to the nth
1010data field in an old-style record.
1011@return offset to the field */
1012ulint
1013rec_get_nth_field_offs_old(
1014/*=======================*/
1015 const rec_t* rec, /*!< in: record */
1016 ulint n, /*!< in: index of the field */
1017 ulint* len) /*!< out: length of the field;
1018 UNIV_SQL_NULL if SQL null */
1019{
1020 ulint os;
1021 ulint next_os;
1022
1023 ut_ad(len);
1024 ut_a(rec);
1025 ut_a(n < rec_get_n_fields_old(rec));
1026
1027 if (rec_get_1byte_offs_flag(rec)) {
1028 os = rec_1_get_field_start_offs(rec, n);
1029
1030 next_os = rec_1_get_field_end_info(rec, n);
1031
1032 if (next_os & REC_1BYTE_SQL_NULL_MASK) {
1033 *len = UNIV_SQL_NULL;
1034
1035 return(os);
1036 }
1037
1038 next_os = next_os & ~REC_1BYTE_SQL_NULL_MASK;
1039 } else {
1040 os = rec_2_get_field_start_offs(rec, n);
1041
1042 next_os = rec_2_get_field_end_info(rec, n);
1043
1044 if (next_os & REC_2BYTE_SQL_NULL_MASK) {
1045 *len = UNIV_SQL_NULL;
1046
1047 return(os);
1048 }
1049
1050 next_os = next_os & ~(REC_2BYTE_SQL_NULL_MASK
1051 | REC_2BYTE_EXTERN_MASK);
1052 }
1053
1054 *len = next_os - os;
1055
1056 ut_ad(*len < srv_page_size);
1057
1058 return(os);
1059}
1060
1061/**********************************************************//**
1062Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT.
1063@return total size */
1064MY_ATTRIBUTE((warn_unused_result, nonnull(1,2)))
1065static inline
1066ulint
1067rec_get_converted_size_comp_prefix_low(
1068/*===================================*/
1069 const dict_index_t* index, /*!< in: record descriptor;
1070 dict_table_is_comp() is
1071 assumed to hold, even if
1072 it does not */
1073 const dfield_t* fields, /*!< in: array of data fields */
1074 ulint n_fields,/*!< in: number of data fields */
1075 ulint* extra, /*!< out: extra size */
1076 rec_comp_status_t status, /*!< in: status flags */
1077 bool temp) /*!< in: whether this is a
1078 temporary file record */
1079{
1080 ulint extra_size = temp ? 0 : REC_N_NEW_EXTRA_BYTES;
1081 ulint data_size;
1082 ulint i;
1083 ut_ad(n_fields > 0);
1084 ut_ad(n_fields <= dict_index_get_n_fields(index));
1085 ut_d(ulint n_null = index->n_nullable);
1086 ut_ad(status == REC_STATUS_ORDINARY || status == REC_STATUS_NODE_PTR
1087 || status == REC_STATUS_COLUMNS_ADDED);
1088
1089 if (status == REC_STATUS_COLUMNS_ADDED
1090 && (!temp || n_fields > index->n_core_fields)) {
1091 ut_ad(index->is_instant());
1092 ut_ad(UT_BITS_IN_BYTES(n_null) >= index->n_core_null_bytes);
1093 extra_size += UT_BITS_IN_BYTES(index->get_n_nullable(n_fields))
1094 + rec_get_n_add_field_len(n_fields - 1
1095 - index->n_core_fields);
1096 } else {
1097 ut_ad(n_fields <= index->n_core_fields);
1098 extra_size += index->n_core_null_bytes;
1099 }
1100
1101 data_size = 0;
1102
1103 if (temp && dict_table_is_comp(index->table)) {
1104 /* No need to do adjust fixed_len=0. We only need to
1105 adjust it for ROW_FORMAT=REDUNDANT. */
1106 temp = false;
1107 }
1108
1109 /* read the lengths of fields 0..n */
1110 for (i = 0; i < n_fields; i++) {
1111 const dict_field_t* field;
1112 ulint len;
1113 ulint fixed_len;
1114 const dict_col_t* col;
1115
1116 field = dict_index_get_nth_field(index, i);
1117 len = dfield_get_len(&fields[i]);
1118 col = dict_field_get_col(field);
1119
1120#ifdef UNIV_DEBUG
1121 dtype_t* type;
1122
1123 type = dfield_get_type(&fields[i]);
1124 if (dict_index_is_spatial(index)) {
1125 if (DATA_GEOMETRY_MTYPE(col->mtype) && i == 0) {
1126 ut_ad(type->prtype & DATA_GIS_MBR);
1127 } else {
1128 ut_ad(type->mtype == DATA_SYS_CHILD
1129 || dict_col_type_assert_equal(col, type));
1130 }
1131 } else {
1132 ut_ad(dict_col_type_assert_equal(col, type));
1133 }
1134#endif
1135
1136 /* All NULLable fields must be included in the n_null count. */
1137 ut_ad((col->prtype & DATA_NOT_NULL) || n_null--);
1138
1139 if (dfield_is_null(&fields[i])) {
1140 /* No length is stored for NULL fields. */
1141 ut_ad(!(col->prtype & DATA_NOT_NULL));
1142 continue;
1143 }
1144
1145 ut_ad(len <= col->len || DATA_LARGE_MTYPE(col->mtype)
1146 || (col->len == 0 && col->mtype == DATA_VARCHAR));
1147
1148 fixed_len = field->fixed_len;
1149 if (temp && fixed_len
1150 && !dict_col_get_fixed_size(col, temp)) {
1151 fixed_len = 0;
1152 }
1153 /* If the maximum length of a variable-length field
1154 is up to 255 bytes, the actual length is always stored
1155 in one byte. If the maximum length is more than 255
1156 bytes, the actual length is stored in one byte for
1157 0..127. The length will be encoded in two bytes when
1158 it is 128 or more, or when the field is stored externally. */
1159
1160 if (fixed_len) {
1161#ifdef UNIV_DEBUG
1162 ut_ad(len <= fixed_len);
1163
1164 if (dict_index_is_spatial(index)) {
1165 ut_ad(type->mtype == DATA_SYS_CHILD
1166 || !col->mbmaxlen
1167 || len >= col->mbminlen
1168 * fixed_len / col->mbmaxlen);
1169 } else {
1170 ut_ad(type->mtype != DATA_SYS_CHILD);
1171 ut_ad(!col->mbmaxlen
1172 || len >= col->mbminlen
1173 * fixed_len / col->mbmaxlen);
1174 }
1175
1176 /* dict_index_add_col() should guarantee this */
1177 ut_ad(!field->prefix_len
1178 || fixed_len == field->prefix_len);
1179#endif /* UNIV_DEBUG */
1180 } else if (dfield_is_ext(&fields[i])) {
1181 ut_ad(DATA_BIG_COL(col));
1182 extra_size += 2;
1183 } else if (len < 128 || !DATA_BIG_COL(col)) {
1184 extra_size++;
1185 } else {
1186 /* For variable-length columns, we look up the
1187 maximum length from the column itself. If this
1188 is a prefix index column shorter than 256 bytes,
1189 this will waste one byte. */
1190 extra_size += 2;
1191 }
1192 data_size += len;
1193 }
1194
1195 if (extra) {
1196 *extra = extra_size;
1197 }
1198
1199 return(extra_size + data_size);
1200}
1201
1202/**********************************************************//**
1203Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT.
1204@return total size */
1205ulint
1206rec_get_converted_size_comp_prefix(
1207/*===============================*/
1208 const dict_index_t* index, /*!< in: record descriptor */
1209 const dfield_t* fields, /*!< in: array of data fields */
1210 ulint n_fields,/*!< in: number of data fields */
1211 ulint* extra) /*!< out: extra size */
1212{
1213 ut_ad(dict_table_is_comp(index->table));
1214 return(rec_get_converted_size_comp_prefix_low(
1215 index, fields, n_fields, extra,
1216 REC_STATUS_ORDINARY, false));
1217}
1218
1219/**********************************************************//**
1220Determines the size of a data tuple in ROW_FORMAT=COMPACT.
1221@return total size */
1222ulint
1223rec_get_converted_size_comp(
1224/*========================*/
1225 const dict_index_t* index, /*!< in: record descriptor;
1226 dict_table_is_comp() is
1227 assumed to hold, even if
1228 it does not */
1229 rec_comp_status_t status, /*!< in: status bits of the record */
1230 const dfield_t* fields, /*!< in: array of data fields */
1231 ulint n_fields,/*!< in: number of data fields */
1232 ulint* extra) /*!< out: extra size */
1233{
1234 ut_ad(n_fields > 0);
1235
1236 switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) {
1237 case REC_STATUS_ORDINARY:
1238 if (n_fields > index->n_core_fields) {
1239 ut_ad(index->is_instant());
1240 status = REC_STATUS_COLUMNS_ADDED;
1241 }
1242 /* fall through */
1243 case REC_STATUS_COLUMNS_ADDED:
1244 ut_ad(n_fields >= index->n_core_fields);
1245 ut_ad(n_fields <= index->n_fields);
1246 return rec_get_converted_size_comp_prefix_low(
1247 index, fields, n_fields, extra, status, false);
1248 case REC_STATUS_NODE_PTR:
1249 n_fields--;
1250 ut_ad(n_fields == dict_index_get_n_unique_in_tree_nonleaf(
1251 index));
1252 ut_ad(dfield_get_len(&fields[n_fields]) == REC_NODE_PTR_SIZE);
1253 return REC_NODE_PTR_SIZE /* child page number */
1254 + rec_get_converted_size_comp_prefix_low(
1255 index, fields, n_fields, extra, status, false);
1256 case REC_STATUS_INFIMUM:
1257 case REC_STATUS_SUPREMUM:
1258 /* not supported */
1259 break;
1260 }
1261
1262 ut_error;
1263 return(ULINT_UNDEFINED);
1264}
1265
1266/***********************************************************//**
1267Sets the value of the ith field SQL null bit of an old-style record. */
1268void
1269rec_set_nth_field_null_bit(
1270/*=======================*/
1271 rec_t* rec, /*!< in: record */
1272 ulint i, /*!< in: ith field */
1273 ibool val) /*!< in: value to set */
1274{
1275 ulint info;
1276
1277 if (rec_get_1byte_offs_flag(rec)) {
1278
1279 info = rec_1_get_field_end_info(rec, i);
1280
1281 if (val) {
1282 info = info | REC_1BYTE_SQL_NULL_MASK;
1283 } else {
1284 info = info & ~REC_1BYTE_SQL_NULL_MASK;
1285 }
1286
1287 rec_1_set_field_end_info(rec, i, info);
1288
1289 return;
1290 }
1291
1292 info = rec_2_get_field_end_info(rec, i);
1293
1294 if (val) {
1295 info = info | REC_2BYTE_SQL_NULL_MASK;
1296 } else {
1297 info = info & ~REC_2BYTE_SQL_NULL_MASK;
1298 }
1299
1300 rec_2_set_field_end_info(rec, i, info);
1301}
1302
1303/***********************************************************//**
1304Sets an old-style record field to SQL null.
1305The physical size of the field is not changed. */
1306void
1307rec_set_nth_field_sql_null(
1308/*=======================*/
1309 rec_t* rec, /*!< in: record */
1310 ulint n) /*!< in: index of the field */
1311{
1312 ulint offset;
1313
1314 offset = rec_get_field_start_offs(rec, n);
1315
1316 data_write_sql_null(rec + offset, rec_get_nth_field_size(rec, n));
1317
1318 rec_set_nth_field_null_bit(rec, n, TRUE);
1319}
1320
1321/*********************************************************//**
1322Builds an old-style physical record out of a data tuple and
1323stores it beginning from the start of the given buffer.
1324@return pointer to the origin of physical record */
1325static
1326rec_t*
1327rec_convert_dtuple_to_rec_old(
1328/*==========================*/
1329 byte* buf, /*!< in: start address of the physical record */
1330 const dtuple_t* dtuple, /*!< in: data tuple */
1331 ulint n_ext) /*!< in: number of externally stored columns */
1332{
1333 const dfield_t* field;
1334 ulint n_fields;
1335 ulint data_size;
1336 rec_t* rec;
1337 ulint end_offset;
1338 ulint ored_offset;
1339 ulint len;
1340 ulint i;
1341
1342 ut_ad(buf && dtuple);
1343 ut_ad(dtuple_validate(dtuple));
1344 ut_ad(dtuple_check_typed(dtuple));
1345
1346 n_fields = dtuple_get_n_fields(dtuple);
1347 data_size = dtuple_get_data_size(dtuple, 0);
1348
1349 ut_ad(n_fields > 0);
1350
1351 /* Calculate the offset of the origin in the physical record */
1352
1353 rec = buf + rec_get_converted_extra_size(data_size, n_fields, n_ext);
1354 /* Store the number of fields */
1355 rec_set_n_fields_old(rec, n_fields);
1356
1357 /* Set the info bits of the record */
1358 rec_set_info_bits_old(rec, dtuple_get_info_bits(dtuple)
1359 & REC_INFO_BITS_MASK);
1360 rec_set_heap_no_old(rec, PAGE_HEAP_NO_USER_LOW);
1361
1362 /* Store the data and the offsets */
1363
1364 end_offset = 0;
1365
1366 if (!n_ext && data_size <= REC_1BYTE_OFFS_LIMIT) {
1367
1368 rec_set_1byte_offs_flag(rec, TRUE);
1369
1370 for (i = 0; i < n_fields; i++) {
1371
1372 field = dtuple_get_nth_field(dtuple, i);
1373
1374 if (dfield_is_null(field)) {
1375 len = dtype_get_sql_null_size(
1376 dfield_get_type(field), 0);
1377 data_write_sql_null(rec + end_offset, len);
1378
1379 end_offset += len;
1380 ored_offset = end_offset
1381 | REC_1BYTE_SQL_NULL_MASK;
1382 } else {
1383 /* If the data is not SQL null, store it */
1384 len = dfield_get_len(field);
1385
1386 memcpy(rec + end_offset,
1387 dfield_get_data(field), len);
1388
1389 end_offset += len;
1390 ored_offset = end_offset;
1391 }
1392
1393 rec_1_set_field_end_info(rec, i, ored_offset);
1394 }
1395 } else {
1396 rec_set_1byte_offs_flag(rec, FALSE);
1397
1398 for (i = 0; i < n_fields; i++) {
1399
1400 field = dtuple_get_nth_field(dtuple, i);
1401
1402 if (dfield_is_null(field)) {
1403 len = dtype_get_sql_null_size(
1404 dfield_get_type(field), 0);
1405 data_write_sql_null(rec + end_offset, len);
1406
1407 end_offset += len;
1408 ored_offset = end_offset
1409 | REC_2BYTE_SQL_NULL_MASK;
1410 } else {
1411 /* If the data is not SQL null, store it */
1412 len = dfield_get_len(field);
1413
1414 memcpy(rec + end_offset,
1415 dfield_get_data(field), len);
1416
1417 end_offset += len;
1418 ored_offset = end_offset;
1419
1420 if (dfield_is_ext(field)) {
1421 ored_offset |= REC_2BYTE_EXTERN_MASK;
1422 }
1423 }
1424
1425 rec_2_set_field_end_info(rec, i, ored_offset);
1426 }
1427 }
1428
1429 return(rec);
1430}
1431
1432/** Convert a data tuple into a ROW_FORMAT=COMPACT record.
1433@param[out] rec converted record
1434@param[in] index index
1435@param[in] fields data fields to convert
1436@param[in] n_fields number of data fields
1437@param[in] status rec_get_status(rec)
1438@param[in] temp whether to use the format for temporary files
1439 in index creation */
1440static inline
1441void
1442rec_convert_dtuple_to_rec_comp(
1443 rec_t* rec,
1444 const dict_index_t* index,
1445 const dfield_t* fields,
1446 ulint n_fields,
1447 rec_comp_status_t status,
1448 bool temp)
1449{
1450 const dfield_t* field;
1451 const dtype_t* type;
1452 byte* end;
1453 byte* nulls = temp
1454 ? rec - 1 : rec - (REC_N_NEW_EXTRA_BYTES + 1);
1455 byte* UNINIT_VAR(lens);
1456 ulint len;
1457 ulint i;
1458 ulint UNINIT_VAR(n_node_ptr_field);
1459 ulint fixed_len;
1460 ulint null_mask = 1;
1461
1462 ut_ad(n_fields > 0);
1463 ut_ad(temp || dict_table_is_comp(index->table));
1464 ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable));
1465
1466 ut_d(ulint n_null = index->n_nullable);
1467
1468 switch (status) {
1469 case REC_STATUS_COLUMNS_ADDED:
1470 ut_ad(index->is_instant());
1471 ut_ad(n_fields > index->n_core_fields);
1472 rec_set_n_add_field(nulls, n_fields - 1
1473 - index->n_core_fields);
1474 /* fall through */
1475 case REC_STATUS_ORDINARY:
1476 ut_ad(n_fields <= dict_index_get_n_fields(index));
1477 if (!temp) {
1478 rec_set_heap_no_new(rec, PAGE_HEAP_NO_USER_LOW);
1479 rec_set_status(rec, n_fields == index->n_core_fields
1480 ? REC_STATUS_ORDINARY
1481 : REC_STATUS_COLUMNS_ADDED);
1482 } if (dict_table_is_comp(index->table)) {
1483 /* No need to do adjust fixed_len=0. We only
1484 need to adjust it for ROW_FORMAT=REDUNDANT. */
1485 temp = false;
1486 }
1487
1488 n_node_ptr_field = ULINT_UNDEFINED;
1489 lens = nulls - (index->is_instant()
1490 ? UT_BITS_IN_BYTES(index->get_n_nullable(
1491 n_fields))
1492 : UT_BITS_IN_BYTES(
1493 unsigned(index->n_nullable)));
1494 break;
1495 case REC_STATUS_NODE_PTR:
1496 ut_ad(!temp);
1497 rec_set_heap_no_new(rec, PAGE_HEAP_NO_USER_LOW);
1498 rec_set_status(rec, status);
1499 ut_ad(n_fields
1500 == dict_index_get_n_unique_in_tree_nonleaf(index) + 1);
1501 ut_d(n_null = std::min(index->n_core_null_bytes * 8U,
1502 index->n_nullable));
1503 n_node_ptr_field = n_fields - 1;
1504 lens = nulls - index->n_core_null_bytes;
1505 break;
1506 case REC_STATUS_INFIMUM:
1507 case REC_STATUS_SUPREMUM:
1508 ut_error;
1509 return;
1510 }
1511
1512 end = rec;
1513 /* clear the SQL-null flags */
1514 memset(lens + 1, 0, ulint(nulls - lens));
1515
1516 /* Store the data and the offsets */
1517
1518 for (i = 0; i < n_fields; i++) {
1519 const dict_field_t* ifield;
1520 dict_col_t* col = NULL;
1521
1522 field = &fields[i];
1523
1524 type = dfield_get_type(field);
1525 len = dfield_get_len(field);
1526
1527 if (UNIV_UNLIKELY(i == n_node_ptr_field)) {
1528 ut_ad(dtype_get_prtype(type) & DATA_NOT_NULL);
1529 ut_ad(len == REC_NODE_PTR_SIZE);
1530 memcpy(end, dfield_get_data(field), len);
1531 end += REC_NODE_PTR_SIZE;
1532 break;
1533 }
1534
1535 if (!(dtype_get_prtype(type) & DATA_NOT_NULL)) {
1536 /* nullable field */
1537 ut_ad(n_null--);
1538
1539 if (UNIV_UNLIKELY(!(byte) null_mask)) {
1540 nulls--;
1541 null_mask = 1;
1542 }
1543
1544 ut_ad(*nulls < null_mask);
1545
1546 /* set the null flag if necessary */
1547 if (dfield_is_null(field)) {
1548 *nulls |= null_mask;
1549 null_mask <<= 1;
1550 continue;
1551 }
1552
1553 null_mask <<= 1;
1554 }
1555 /* only nullable fields can be null */
1556 ut_ad(!dfield_is_null(field));
1557
1558 ifield = dict_index_get_nth_field(index, i);
1559 fixed_len = ifield->fixed_len;
1560 col = ifield->col;
1561 if (temp && fixed_len
1562 && !dict_col_get_fixed_size(col, temp)) {
1563 fixed_len = 0;
1564 }
1565
1566 /* If the maximum length of a variable-length field
1567 is up to 255 bytes, the actual length is always stored
1568 in one byte. If the maximum length is more than 255
1569 bytes, the actual length is stored in one byte for
1570 0..127. The length will be encoded in two bytes when
1571 it is 128 or more, or when the field is stored externally. */
1572 if (fixed_len) {
1573 ut_ad(len <= fixed_len);
1574 ut_ad(!col->mbmaxlen
1575 || len >= col->mbminlen
1576 * fixed_len / col->mbmaxlen);
1577 ut_ad(!dfield_is_ext(field));
1578 } else if (dfield_is_ext(field)) {
1579 ut_ad(DATA_BIG_COL(col));
1580 ut_ad(len <= REC_ANTELOPE_MAX_INDEX_COL_LEN
1581 + BTR_EXTERN_FIELD_REF_SIZE);
1582 *lens-- = (byte) (len >> 8) | 0xc0;
1583 *lens-- = (byte) len;
1584 } else {
1585 ut_ad(len <= dtype_get_len(type)
1586 || DATA_LARGE_MTYPE(dtype_get_mtype(type))
1587 || !strcmp(index->name,
1588 FTS_INDEX_TABLE_IND_NAME));
1589 if (len < 128 || !DATA_BIG_LEN_MTYPE(
1590 dtype_get_len(type), dtype_get_mtype(type))) {
1591
1592 *lens-- = (byte) len;
1593 } else {
1594 ut_ad(len < 16384);
1595 *lens-- = (byte) (len >> 8) | 0x80;
1596 *lens-- = (byte) len;
1597 }
1598 }
1599
1600 if (len) {
1601 memcpy(end, dfield_get_data(field), len);
1602 end += len;
1603 }
1604 }
1605}
1606
1607/*********************************************************//**
1608Builds a new-style physical record out of a data tuple and
1609stores it beginning from the start of the given buffer.
1610@return pointer to the origin of physical record */
1611static
1612rec_t*
1613rec_convert_dtuple_to_rec_new(
1614/*==========================*/
1615 byte* buf, /*!< in: start address of
1616 the physical record */
1617 const dict_index_t* index, /*!< in: record descriptor */
1618 const dtuple_t* dtuple) /*!< in: data tuple */
1619{
1620 ut_ad(!(dtuple->info_bits
1621 & ~(REC_NEW_STATUS_MASK | REC_INFO_DELETED_FLAG
1622 | REC_INFO_MIN_REC_FLAG)));
1623 rec_comp_status_t status = static_cast<rec_comp_status_t>(
1624 dtuple->info_bits & REC_NEW_STATUS_MASK);
1625 if (status == REC_STATUS_ORDINARY
1626 && dtuple->n_fields > index->n_core_fields) {
1627 ut_ad(index->is_instant());
1628 status = REC_STATUS_COLUMNS_ADDED;
1629 }
1630
1631 ulint extra_size;
1632
1633 rec_get_converted_size_comp(
1634 index, status, dtuple->fields, dtuple->n_fields, &extra_size);
1635 rec_t* rec = buf + extra_size;
1636
1637 rec_convert_dtuple_to_rec_comp(
1638 rec, index, dtuple->fields, dtuple->n_fields, status, false);
1639 rec_set_info_bits_new(rec, dtuple->info_bits & ~REC_NEW_STATUS_MASK);
1640 return(rec);
1641}
1642
1643/*********************************************************//**
1644Builds a physical record out of a data tuple and
1645stores it beginning from the start of the given buffer.
1646@return pointer to the origin of physical record */
1647rec_t*
1648rec_convert_dtuple_to_rec(
1649/*======================*/
1650 byte* buf, /*!< in: start address of the
1651 physical record */
1652 const dict_index_t* index, /*!< in: record descriptor */
1653 const dtuple_t* dtuple, /*!< in: data tuple */
1654 ulint n_ext) /*!< in: number of
1655 externally stored columns */
1656{
1657 rec_t* rec;
1658
1659 ut_ad(buf != NULL);
1660 ut_ad(index != NULL);
1661 ut_ad(dtuple != NULL);
1662 ut_ad(dtuple_validate(dtuple));
1663 ut_ad(dtuple_check_typed(dtuple));
1664
1665 if (dict_table_is_comp(index->table)) {
1666 rec = rec_convert_dtuple_to_rec_new(buf, index, dtuple);
1667 } else {
1668 rec = rec_convert_dtuple_to_rec_old(buf, dtuple, n_ext);
1669 }
1670
1671 return(rec);
1672}
1673
1674/** Determine the size of a data tuple prefix in a temporary file.
1675@param[in] index clustered or secondary index
1676@param[in] fields data fields
1677@param[in] n_fields number of data fields
1678@param[out] extra record header size
1679@param[in] status REC_STATUS_ORDINARY or REC_STATUS_COLUMNS_ADDED
1680@return total size, in bytes */
1681ulint
1682rec_get_converted_size_temp(
1683 const dict_index_t* index,
1684 const dfield_t* fields,
1685 ulint n_fields,
1686 ulint* extra,
1687 rec_comp_status_t status)
1688{
1689 return rec_get_converted_size_comp_prefix_low(
1690 index, fields, n_fields, extra, status, true);
1691}
1692
1693/** Determine the offset to each field in temporary file.
1694@param[in] rec temporary file record
1695@param[in] index index of that the record belongs to
1696@param[in,out] offsets offsets to the fields; in: rec_offs_n_fields(offsets)
1697@param[in] n_core number of core fields (index->n_core_fields)
1698@param[in] status REC_STATUS_ORDINARY or REC_STATUS_COLUMNS_ADDED */
1699void
1700rec_init_offsets_temp(
1701 const rec_t* rec,
1702 const dict_index_t* index,
1703 ulint* offsets,
1704 ulint n_core,
1705 rec_comp_status_t status)
1706{
1707 ut_ad(status == REC_STATUS_ORDINARY
1708 || status == REC_STATUS_COLUMNS_ADDED);
1709 /* The table may have been converted to plain format
1710 if it was emptied during an ALTER TABLE operation. */
1711 ut_ad(index->n_core_fields == n_core || !index->is_instant());
1712 ut_ad(index->n_core_fields >= n_core);
1713 rec_init_offsets_comp_ordinary(rec, index, offsets, n_core,
1714 status == REC_STATUS_COLUMNS_ADDED
1715 ? REC_LEAF_TEMP_COLUMNS_ADDED
1716 : REC_LEAF_TEMP);
1717}
1718
1719/** Determine the offset to each field in temporary file.
1720@param[in] rec temporary file record
1721@param[in] index index of that the record belongs to
1722@param[in,out] offsets offsets to the fields; in: rec_offs_n_fields(offsets)
1723*/
1724void
1725rec_init_offsets_temp(
1726 const rec_t* rec,
1727 const dict_index_t* index,
1728 ulint* offsets)
1729{
1730 ut_ad(!index->is_instant());
1731 rec_init_offsets_comp_ordinary(rec, index, offsets,
1732 index->n_core_fields, REC_LEAF_TEMP);
1733}
1734
1735/** Convert a data tuple prefix to the temporary file format.
1736@param[out] rec record in temporary file format
1737@param[in] index clustered or secondary index
1738@param[in] fields data fields
1739@param[in] n_fields number of data fields
1740@param[in] status REC_STATUS_ORDINARY or REC_STATUS_COLUMNS_ADDED
1741*/
1742void
1743rec_convert_dtuple_to_temp(
1744 rec_t* rec,
1745 const dict_index_t* index,
1746 const dfield_t* fields,
1747 ulint n_fields,
1748 rec_comp_status_t status)
1749{
1750 rec_convert_dtuple_to_rec_comp(rec, index, fields, n_fields,
1751 status, true);
1752}
1753
1754/** Copy the first n fields of a (copy of a) physical record to a data tuple.
1755The fields are copied into the memory heap.
1756@param[out] tuple data tuple
1757@param[in] rec index record, or a copy thereof
1758@param[in] is_leaf whether rec is a leaf page record
1759@param[in] n_fields number of fields to copy
1760@param[in,out] heap memory heap */
1761void
1762rec_copy_prefix_to_dtuple(
1763 dtuple_t* tuple,
1764 const rec_t* rec,
1765 const dict_index_t* index,
1766 bool is_leaf,
1767 ulint n_fields,
1768 mem_heap_t* heap)
1769{
1770 ulint offsets_[REC_OFFS_NORMAL_SIZE];
1771 ulint* offsets = offsets_;
1772 rec_offs_init(offsets_);
1773
1774 ut_ad(is_leaf || n_fields
1775 <= dict_index_get_n_unique_in_tree_nonleaf(index) + 1);
1776
1777 offsets = rec_get_offsets(rec, index, offsets, is_leaf,
1778 n_fields, &heap);
1779
1780 ut_ad(rec_validate(rec, offsets));
1781 ut_ad(!rec_offs_any_default(offsets));
1782 ut_ad(dtuple_check_typed(tuple));
1783
1784 tuple->info_bits = rec_get_info_bits(rec, rec_offs_comp(offsets));
1785
1786 for (ulint i = 0; i < n_fields; i++) {
1787 dfield_t* field;
1788 const byte* data;
1789 ulint len;
1790
1791 field = dtuple_get_nth_field(tuple, i);
1792 data = rec_get_nth_field(rec, offsets, i, &len);
1793
1794 if (len != UNIV_SQL_NULL) {
1795 dfield_set_data(field,
1796 mem_heap_dup(heap, data, len), len);
1797 ut_ad(!rec_offs_nth_extern(offsets, i));
1798 } else {
1799 dfield_set_null(field);
1800 }
1801 }
1802}
1803
1804/**************************************************************//**
1805Copies the first n fields of an old-style physical record
1806to a new physical record in a buffer.
1807@return own: copied record */
1808static
1809rec_t*
1810rec_copy_prefix_to_buf_old(
1811/*=======================*/
1812 const rec_t* rec, /*!< in: physical record */
1813 ulint n_fields, /*!< in: number of fields to copy */
1814 ulint area_end, /*!< in: end of the prefix data */
1815 byte** buf, /*!< in/out: memory buffer for
1816 the copied prefix, or NULL */
1817 ulint* buf_size) /*!< in/out: buffer size */
1818{
1819 rec_t* copy_rec;
1820 ulint area_start;
1821 ulint prefix_len;
1822
1823 if (rec_get_1byte_offs_flag(rec)) {
1824 area_start = REC_N_OLD_EXTRA_BYTES + n_fields;
1825 } else {
1826 area_start = REC_N_OLD_EXTRA_BYTES + 2 * n_fields;
1827 }
1828
1829 prefix_len = area_start + area_end;
1830
1831 if ((*buf == NULL) || (*buf_size < prefix_len)) {
1832 ut_free(*buf);
1833 *buf_size = prefix_len;
1834 *buf = static_cast<byte*>(ut_malloc_nokey(prefix_len));
1835 }
1836
1837 ut_memcpy(*buf, rec - area_start, prefix_len);
1838
1839 copy_rec = *buf + area_start;
1840
1841 rec_set_n_fields_old(copy_rec, n_fields);
1842
1843 return(copy_rec);
1844}
1845
1846/**************************************************************//**
1847Copies the first n fields of a physical record to a new physical record in
1848a buffer.
1849@return own: copied record */
1850rec_t*
1851rec_copy_prefix_to_buf(
1852/*===================*/
1853 const rec_t* rec, /*!< in: physical record */
1854 const dict_index_t* index, /*!< in: record descriptor */
1855 ulint n_fields, /*!< in: number of fields
1856 to copy */
1857 byte** buf, /*!< in/out: memory buffer
1858 for the copied prefix,
1859 or NULL */
1860 ulint* buf_size) /*!< in/out: buffer size */
1861{
1862 ut_ad(n_fields <= index->n_fields || dict_index_is_ibuf(index));
1863 ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable));
1864 UNIV_PREFETCH_RW(*buf);
1865
1866 if (!dict_table_is_comp(index->table)) {
1867 ut_ad(rec_validate_old(rec));
1868 return(rec_copy_prefix_to_buf_old(
1869 rec, n_fields,
1870 rec_get_field_start_offs(rec, n_fields),
1871 buf, buf_size));
1872 }
1873
1874 ulint prefix_len = 0;
1875 ulint instant_omit = 0;
1876 const byte* nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
1877 const byte* nullf = nulls;
1878 const byte* lens = nulls - index->n_core_null_bytes;
1879
1880 switch (rec_get_status(rec)) {
1881 default:
1882 /* infimum or supremum record: no sense to copy anything */
1883 ut_error;
1884 return(NULL);
1885 case REC_STATUS_ORDINARY:
1886 ut_ad(n_fields <= index->n_core_fields);
1887 break;
1888 case REC_STATUS_NODE_PTR:
1889 /* For R-tree, we need to copy the child page number field. */
1890 compile_time_assert(DICT_INDEX_SPATIAL_NODEPTR_SIZE == 1);
1891 if (dict_index_is_spatial(index)) {
1892 ut_ad(index->n_core_null_bytes == 0);
1893 ut_ad(n_fields == DICT_INDEX_SPATIAL_NODEPTR_SIZE + 1);
1894 ut_ad(index->fields[0].col->prtype & DATA_NOT_NULL);
1895 ut_ad(DATA_BIG_COL(index->fields[0].col));
1896 /* This is a deficiency of the format introduced
1897 in MySQL 5.7. The length in the R-tree index should
1898 always be DATA_MBR_LEN. */
1899 ut_ad(!index->fields[0].fixed_len);
1900 ut_ad(*lens == DATA_MBR_LEN);
1901 lens--;
1902 prefix_len = DATA_MBR_LEN + REC_NODE_PTR_SIZE;
1903 n_fields = 0; /* skip the "for" loop below */
1904 break;
1905 }
1906 /* it doesn't make sense to copy the child page number field */
1907 ut_ad(n_fields
1908 <= dict_index_get_n_unique_in_tree_nonleaf(index));
1909 break;
1910 case REC_STATUS_COLUMNS_ADDED:
1911 /* We would have !index->is_instant() when rolling back
1912 an instant ADD COLUMN operation. */
1913 ut_ad(index->is_instant() || page_rec_is_default_row(rec));
1914 nulls++;
1915 const ulint n_rec = ulint(index->n_core_fields) + 1
1916 + rec_get_n_add_field(nulls);
1917 instant_omit = ulint(&rec[-REC_N_NEW_EXTRA_BYTES] - nulls);
1918 ut_ad(instant_omit == 1 || instant_omit == 2);
1919 nullf = nulls;
1920 const uint nb = UT_BITS_IN_BYTES(index->get_n_nullable(n_rec));
1921 instant_omit += nb - index->n_core_null_bytes;
1922 lens = --nulls - nb;
1923 }
1924
1925 const byte* const lenf = lens;
1926 UNIV_PREFETCH_R(lens);
1927
1928 /* read the lengths of fields 0..n */
1929 for (ulint i = 0, null_mask = 1; i < n_fields; i++) {
1930 const dict_field_t* field;
1931 const dict_col_t* col;
1932
1933 field = dict_index_get_nth_field(index, i);
1934 col = dict_field_get_col(field);
1935
1936 if (!(col->prtype & DATA_NOT_NULL)) {
1937 /* nullable field => read the null flag */
1938 if (UNIV_UNLIKELY(!(byte) null_mask)) {
1939 nulls--;
1940 null_mask = 1;
1941 }
1942
1943 if (*nulls & null_mask) {
1944 null_mask <<= 1;
1945 continue;
1946 }
1947
1948 null_mask <<= 1;
1949 }
1950
1951 if (field->fixed_len) {
1952 prefix_len += field->fixed_len;
1953 } else {
1954 ulint len = *lens--;
1955 /* If the maximum length of the column is up
1956 to 255 bytes, the actual length is always
1957 stored in one byte. If the maximum length is
1958 more than 255 bytes, the actual length is
1959 stored in one byte for 0..127. The length
1960 will be encoded in two bytes when it is 128 or
1961 more, or when the column is stored externally. */
1962 if (DATA_BIG_COL(col)) {
1963 if (len & 0x80) {
1964 /* 1exxxxxx */
1965 len &= 0x3f;
1966 len <<= 8;
1967 len |= *lens--;
1968 UNIV_PREFETCH_R(lens);
1969 }
1970 }
1971 prefix_len += len;
1972 }
1973 }
1974
1975 UNIV_PREFETCH_R(rec + prefix_len);
1976
1977 ulint size = prefix_len + ulint(rec - (lens + 1)) - instant_omit;
1978
1979 if (*buf == NULL || *buf_size < size) {
1980 ut_free(*buf);
1981 *buf_size = size;
1982 *buf = static_cast<byte*>(ut_malloc_nokey(size));
1983 }
1984
1985 if (instant_omit) {
1986 /* Copy and convert the record header to a format where
1987 instant ADD COLUMN has not been used:
1988 + lengths of variable-length fields in the prefix
1989 - omit any null flag bytes for any instantly added columns
1990 + index->n_core_null_bytes of null flags
1991 - omit the n_add_fields header (1 or 2 bytes)
1992 + REC_N_NEW_EXTRA_BYTES of fixed header */
1993 byte* b = *buf;
1994 /* copy the lengths of the variable-length fields */
1995 memcpy(b, lens + 1, ulint(lenf - lens));
1996 b += ulint(lenf - lens);
1997 /* copy the null flags */
1998 memcpy(b, nullf - index->n_core_null_bytes,
1999 index->n_core_null_bytes);
2000 b += index->n_core_null_bytes + REC_N_NEW_EXTRA_BYTES;
2001 ut_ad(ulint(b - *buf) + prefix_len == size);
2002 /* copy the fixed-size header and the record prefix */
2003 memcpy(b - REC_N_NEW_EXTRA_BYTES, rec - REC_N_NEW_EXTRA_BYTES,
2004 prefix_len + REC_N_NEW_EXTRA_BYTES);
2005 ut_ad(rec_get_status(b) == REC_STATUS_COLUMNS_ADDED);
2006 rec_set_status(b, REC_STATUS_ORDINARY);
2007 return b;
2008 } else {
2009 memcpy(*buf, lens + 1, size);
2010 return *buf + (rec - (lens + 1));
2011 }
2012}
2013
2014/***************************************************************//**
2015Validates the consistency of an old-style physical record.
2016@return TRUE if ok */
2017static
2018ibool
2019rec_validate_old(
2020/*=============*/
2021 const rec_t* rec) /*!< in: physical record */
2022{
2023 ulint len;
2024 ulint n_fields;
2025 ulint len_sum = 0;
2026 ulint i;
2027
2028 ut_a(rec);
2029 n_fields = rec_get_n_fields_old(rec);
2030
2031 if ((n_fields == 0) || (n_fields > REC_MAX_N_FIELDS)) {
2032 ib::error() << "Record has " << n_fields << " fields";
2033 return(FALSE);
2034 }
2035
2036 for (i = 0; i < n_fields; i++) {
2037 rec_get_nth_field_offs_old(rec, i, &len);
2038
2039 if (!((len < srv_page_size) || (len == UNIV_SQL_NULL))) {
2040 ib::error() << "Record field " << i << " len " << len;
2041 return(FALSE);
2042 }
2043
2044 if (len != UNIV_SQL_NULL) {
2045 len_sum += len;
2046 } else {
2047 len_sum += rec_get_nth_field_size(rec, i);
2048 }
2049 }
2050
2051 if (len_sum != rec_get_data_size_old(rec)) {
2052 ib::error() << "Record len should be " << len_sum << ", len "
2053 << rec_get_data_size_old(rec);
2054 return(FALSE);
2055 }
2056
2057 return(TRUE);
2058}
2059
2060/***************************************************************//**
2061Validates the consistency of a physical record.
2062@return TRUE if ok */
2063ibool
2064rec_validate(
2065/*=========*/
2066 const rec_t* rec, /*!< in: physical record */
2067 const ulint* offsets)/*!< in: array returned by rec_get_offsets() */
2068{
2069 ulint len;
2070 ulint n_fields;
2071 ulint len_sum = 0;
2072 ulint i;
2073
2074 ut_a(rec);
2075 n_fields = rec_offs_n_fields(offsets);
2076
2077 if ((n_fields == 0) || (n_fields > REC_MAX_N_FIELDS)) {
2078 ib::error() << "Record has " << n_fields << " fields";
2079 return(FALSE);
2080 }
2081
2082 ut_a(rec_offs_any_flag(offsets, REC_OFFS_COMPACT | REC_OFFS_DEFAULT)
2083 || n_fields <= rec_get_n_fields_old(rec));
2084
2085 for (i = 0; i < n_fields; i++) {
2086 rec_get_nth_field_offs(offsets, i, &len);
2087
2088 switch (len) {
2089 default:
2090 if (len >= srv_page_size) {
2091 ib::error() << "Record field " << i
2092 << " len " << len;
2093 return(FALSE);
2094 }
2095 len_sum += len;
2096 break;
2097 case UNIV_SQL_DEFAULT:
2098 break;
2099 case UNIV_SQL_NULL:
2100 if (!rec_offs_comp(offsets)) {
2101 len_sum += rec_get_nth_field_size(rec, i);
2102 }
2103 }
2104 }
2105
2106 if (len_sum != rec_offs_data_size(offsets)) {
2107 ib::error() << "Record len should be " << len_sum << ", len "
2108 << rec_offs_data_size(offsets);
2109 return(FALSE);
2110 }
2111
2112 if (!rec_offs_comp(offsets)) {
2113 ut_a(rec_validate_old(rec));
2114 }
2115
2116 return(TRUE);
2117}
2118
2119/***************************************************************//**
2120Prints an old-style physical record. */
2121void
2122rec_print_old(
2123/*==========*/
2124 FILE* file, /*!< in: file where to print */
2125 const rec_t* rec) /*!< in: physical record */
2126{
2127 const byte* data;
2128 ulint len;
2129 ulint n;
2130 ulint i;
2131
2132 ut_ad(rec);
2133
2134 n = rec_get_n_fields_old(rec);
2135
2136 fprintf(file, "PHYSICAL RECORD: n_fields " ULINTPF ";"
2137 " %u-byte offsets; info bits " ULINTPF "\n",
2138 n,
2139 rec_get_1byte_offs_flag(rec) ? 1 : 2,
2140 rec_get_info_bits(rec, FALSE));
2141
2142 for (i = 0; i < n; i++) {
2143
2144 data = rec_get_nth_field_old(rec, i, &len);
2145
2146 fprintf(file, " " ULINTPF ":", i);
2147
2148 if (len != UNIV_SQL_NULL) {
2149 if (len <= 30) {
2150
2151 ut_print_buf(file, data, len);
2152 } else {
2153 ut_print_buf(file, data, 30);
2154
2155 fprintf(file, " (total " ULINTPF " bytes)",
2156 len);
2157 }
2158 } else {
2159 fprintf(file, " SQL NULL, size " ULINTPF " ",
2160 rec_get_nth_field_size(rec, i));
2161 }
2162
2163 putc(';', file);
2164 putc('\n', file);
2165 }
2166
2167 rec_validate_old(rec);
2168}
2169
2170/***************************************************************//**
2171Prints a physical record in ROW_FORMAT=COMPACT. Ignores the
2172record header. */
2173static
2174void
2175rec_print_comp(
2176/*===========*/
2177 FILE* file, /*!< in: file where to print */
2178 const rec_t* rec, /*!< in: physical record */
2179 const ulint* offsets)/*!< in: array returned by rec_get_offsets() */
2180{
2181 ulint i;
2182
2183 for (i = 0; i < rec_offs_n_fields(offsets); i++) {
2184 const byte* UNINIT_VAR(data);
2185 ulint len;
2186
2187 if (rec_offs_nth_default(offsets, i)) {
2188 len = UNIV_SQL_DEFAULT;
2189 } else {
2190 data = rec_get_nth_field(rec, offsets, i, &len);
2191 }
2192
2193 fprintf(file, " " ULINTPF ":", i);
2194
2195 if (len == UNIV_SQL_NULL) {
2196 fputs(" SQL NULL", file);
2197 } else if (len == UNIV_SQL_DEFAULT) {
2198 fputs(" SQL DEFAULT", file);
2199 } else {
2200 if (len <= 30) {
2201
2202 ut_print_buf(file, data, len);
2203 } else if (rec_offs_nth_extern(offsets, i)) {
2204 ut_print_buf(file, data, 30);
2205 fprintf(file,
2206 " (total " ULINTPF " bytes, external)",
2207 len);
2208 ut_print_buf(file, data + len
2209 - BTR_EXTERN_FIELD_REF_SIZE,
2210 BTR_EXTERN_FIELD_REF_SIZE);
2211 } else {
2212 ut_print_buf(file, data, 30);
2213
2214 fprintf(file, " (total " ULINTPF " bytes)",
2215 len);
2216 }
2217 }
2218 putc(';', file);
2219 putc('\n', file);
2220 }
2221}
2222
2223/***************************************************************//**
2224Prints an old-style spatial index record. */
2225static
2226void
2227rec_print_mbr_old(
2228/*==============*/
2229 FILE* file, /*!< in: file where to print */
2230 const rec_t* rec) /*!< in: physical record */
2231{
2232 const byte* data;
2233 ulint len;
2234 ulint n;
2235 ulint i;
2236
2237 ut_ad(rec);
2238
2239 n = rec_get_n_fields_old(rec);
2240
2241 fprintf(file, "PHYSICAL RECORD: n_fields %lu;"
2242 " %u-byte offsets; info bits %lu\n",
2243 (ulong) n,
2244 rec_get_1byte_offs_flag(rec) ? 1 : 2,
2245 (ulong) rec_get_info_bits(rec, FALSE));
2246
2247 for (i = 0; i < n; i++) {
2248
2249 data = rec_get_nth_field_old(rec, i, &len);
2250
2251 fprintf(file, " %lu:", (ulong) i);
2252
2253 if (len != UNIV_SQL_NULL) {
2254 if (i == 0) {
2255 fprintf(file, " MBR:");
2256 for (; len > 0; len -= sizeof(double)) {
2257 double d = mach_double_read(data);
2258
2259 if (len != sizeof(double)) {
2260 fprintf(file, "%.2lf,", d);
2261 } else {
2262 fprintf(file, "%.2lf", d);
2263 }
2264
2265 data += sizeof(double);
2266 }
2267 } else {
2268 if (len <= 30) {
2269
2270 ut_print_buf(file, data, len);
2271 } else {
2272 ut_print_buf(file, data, 30);
2273
2274 fprintf(file, " (total %lu bytes)",
2275 (ulong) len);
2276 }
2277 }
2278 } else {
2279 fprintf(file, " SQL NULL, size " ULINTPF " ",
2280 rec_get_nth_field_size(rec, i));
2281 }
2282
2283 putc(';', file);
2284 putc('\n', file);
2285 }
2286
2287 if (rec_get_deleted_flag(rec, false)) {
2288 fprintf(file, " Deleted");
2289 }
2290
2291 if (rec_get_info_bits(rec, true) & REC_INFO_MIN_REC_FLAG) {
2292 fprintf(file, " First rec");
2293 }
2294
2295 rec_validate_old(rec);
2296}
2297
2298/***************************************************************//**
2299Prints a spatial index record. */
2300void
2301rec_print_mbr_rec(
2302/*==============*/
2303 FILE* file, /*!< in: file where to print */
2304 const rec_t* rec, /*!< in: physical record */
2305 const ulint* offsets)/*!< in: array returned by rec_get_offsets() */
2306{
2307 ut_ad(rec);
2308 ut_ad(offsets);
2309 ut_ad(rec_offs_validate(rec, NULL, offsets));
2310 ut_ad(!rec_offs_any_default(offsets));
2311
2312 if (!rec_offs_comp(offsets)) {
2313 rec_print_mbr_old(file, rec);
2314 return;
2315 }
2316
2317 for (ulint i = 0; i < rec_offs_n_fields(offsets); i++) {
2318 const byte* data;
2319 ulint len;
2320
2321 data = rec_get_nth_field(rec, offsets, i, &len);
2322
2323 if (i == 0) {
2324 fprintf(file, " MBR:");
2325 for (; len > 0; len -= sizeof(double)) {
2326 double d = mach_double_read(data);
2327
2328 if (len != sizeof(double)) {
2329 fprintf(file, "%.2lf,", d);
2330 } else {
2331 fprintf(file, "%.2lf", d);
2332 }
2333
2334 data += sizeof(double);
2335 }
2336 } else {
2337 fprintf(file, " %lu:", (ulong) i);
2338
2339 if (len != UNIV_SQL_NULL) {
2340 if (len <= 30) {
2341
2342 ut_print_buf(file, data, len);
2343 } else {
2344 ut_print_buf(file, data, 30);
2345
2346 fprintf(file, " (total %lu bytes)",
2347 (ulong) len);
2348 }
2349 } else {
2350 fputs(" SQL NULL", file);
2351 }
2352 }
2353 putc(';', file);
2354 }
2355
2356 if (rec_get_info_bits(rec, true) & REC_INFO_DELETED_FLAG) {
2357 fprintf(file, " Deleted");
2358 }
2359
2360 if (rec_get_info_bits(rec, true) & REC_INFO_MIN_REC_FLAG) {
2361 fprintf(file, " First rec");
2362 }
2363
2364
2365 rec_validate(rec, offsets);
2366}
2367
2368/***************************************************************//**
2369Prints a physical record. */
2370void
2371rec_print_new(
2372/*==========*/
2373 FILE* file, /*!< in: file where to print */
2374 const rec_t* rec, /*!< in: physical record */
2375 const ulint* offsets)/*!< in: array returned by rec_get_offsets() */
2376{
2377 ut_ad(rec);
2378 ut_ad(offsets);
2379 ut_ad(rec_offs_validate(rec, NULL, offsets));
2380
2381#ifdef UNIV_DEBUG
2382 if (rec_get_deleted_flag(rec, rec_offs_comp(offsets))) {
2383 DBUG_PRINT("info", ("deleted "));
2384 } else {
2385 DBUG_PRINT("info", ("not-deleted "));
2386 }
2387#endif /* UNIV_DEBUG */
2388
2389 if (!rec_offs_comp(offsets)) {
2390 rec_print_old(file, rec);
2391 return;
2392 }
2393
2394 fprintf(file, "PHYSICAL RECORD: n_fields " ULINTPF ";"
2395 " compact format; info bits " ULINTPF "\n",
2396 rec_offs_n_fields(offsets),
2397 rec_get_info_bits(rec, TRUE));
2398
2399 rec_print_comp(file, rec, offsets);
2400 rec_validate(rec, offsets);
2401}
2402
2403/***************************************************************//**
2404Prints a physical record. */
2405void
2406rec_print(
2407/*======*/
2408 FILE* file, /*!< in: file where to print */
2409 const rec_t* rec, /*!< in: physical record */
2410 const dict_index_t* index) /*!< in: record descriptor */
2411{
2412 ut_ad(index);
2413
2414 if (!dict_table_is_comp(index->table)) {
2415 rec_print_old(file, rec);
2416 return;
2417 } else {
2418 mem_heap_t* heap = NULL;
2419 ulint offsets_[REC_OFFS_NORMAL_SIZE];
2420 rec_offs_init(offsets_);
2421
2422 rec_print_new(file, rec,
2423 rec_get_offsets(rec, index, offsets_,
2424 page_rec_is_leaf(rec),
2425 ULINT_UNDEFINED, &heap));
2426 if (UNIV_LIKELY_NULL(heap)) {
2427 mem_heap_free(heap);
2428 }
2429 }
2430}
2431
2432/** Pretty-print a record.
2433@param[in,out] o output stream
2434@param[in] rec physical record
2435@param[in] info rec_get_info_bits(rec)
2436@param[in] offsets rec_get_offsets(rec) */
2437void
2438rec_print(
2439 std::ostream& o,
2440 const rec_t* rec,
2441 ulint info,
2442 const ulint* offsets)
2443{
2444 const ulint comp = rec_offs_comp(offsets);
2445 const ulint n = rec_offs_n_fields(offsets);
2446
2447 ut_ad(rec_offs_validate(rec, NULL, offsets));
2448
2449 o << (comp ? "COMPACT RECORD" : "RECORD")
2450 << "(info_bits=" << info << ", " << n << " fields): {";
2451
2452 for (ulint i = 0; i < n; i++) {
2453 const byte* data;
2454 ulint len;
2455
2456 if (i) {
2457 o << ',';
2458 }
2459
2460 data = rec_get_nth_field(rec, offsets, i, &len);
2461
2462 if (len == UNIV_SQL_DEFAULT) {
2463 o << "DEFAULT";
2464 continue;
2465 }
2466
2467 if (len == UNIV_SQL_NULL) {
2468 o << "NULL";
2469 continue;
2470 }
2471
2472 if (rec_offs_nth_extern(offsets, i)) {
2473 ulint local_len = len - BTR_EXTERN_FIELD_REF_SIZE;
2474 ut_ad(len >= BTR_EXTERN_FIELD_REF_SIZE);
2475
2476 o << '['
2477 << local_len
2478 << '+' << BTR_EXTERN_FIELD_REF_SIZE << ']';
2479 ut_print_buf(o, data, local_len);
2480 ut_print_buf_hex(o, data + local_len,
2481 BTR_EXTERN_FIELD_REF_SIZE);
2482 } else {
2483 o << '[' << len << ']';
2484 ut_print_buf(o, data, len);
2485 }
2486 }
2487
2488 o << "}";
2489}
2490
2491/** Display a record.
2492@param[in,out] o output stream
2493@param[in] r record to display
2494@return the output stream */
2495std::ostream&
2496operator<<(std::ostream& o, const rec_index_print& r)
2497{
2498 mem_heap_t* heap = NULL;
2499 ulint* offsets = rec_get_offsets(
2500 r.m_rec, r.m_index, NULL, page_rec_is_leaf(r.m_rec),
2501 ULINT_UNDEFINED, &heap);
2502 rec_print(o, r.m_rec,
2503 rec_get_info_bits(r.m_rec, rec_offs_comp(offsets)),
2504 offsets);
2505 mem_heap_free(heap);
2506 return(o);
2507}
2508
2509/** Display a record.
2510@param[in,out] o output stream
2511@param[in] r record to display
2512@return the output stream */
2513std::ostream&
2514operator<<(std::ostream& o, const rec_offsets_print& r)
2515{
2516 rec_print(o, r.m_rec,
2517 rec_get_info_bits(r.m_rec, rec_offs_comp(r.m_offsets)),
2518 r.m_offsets);
2519 return(o);
2520}
2521
2522#ifdef UNIV_DEBUG
2523/** Read the DB_TRX_ID of a clustered index record.
2524@param[in] rec clustered index record
2525@param[in] index clustered index
2526@return the value of DB_TRX_ID */
2527trx_id_t
2528rec_get_trx_id(
2529 const rec_t* rec,
2530 const dict_index_t* index)
2531{
2532 ulint trx_id_col
2533 = dict_index_get_sys_col_pos(index, DATA_TRX_ID);
2534 const byte* trx_id;
2535 ulint len;
2536 mem_heap_t* heap = NULL;
2537 ulint offsets_[REC_OFFS_HEADER_SIZE + MAX_REF_PARTS + 2];
2538 rec_offs_init(offsets_);
2539 ulint* offsets = offsets_;
2540
2541 ut_ad(trx_id_col <= MAX_REF_PARTS);
2542 ut_ad(dict_index_is_clust(index));
2543 ut_ad(trx_id_col > 0);
2544 ut_ad(trx_id_col != ULINT_UNDEFINED);
2545
2546 offsets = rec_get_offsets(rec, index, offsets, true,
2547 trx_id_col + 1, &heap);
2548
2549 trx_id = rec_get_nth_field(rec, offsets, trx_id_col, &len);
2550
2551 ut_ad(len == DATA_TRX_ID_LEN);
2552
2553 if (UNIV_LIKELY_NULL(heap)) {
2554 mem_heap_free(heap);
2555 }
2556
2557 return(trx_read_trx_id(trx_id));
2558}
2559#endif /* UNIV_DEBUG */
2560
2561/** Mark the nth field as externally stored.
2562@param[in] offsets array returned by rec_get_offsets()
2563@param[in] n nth field */
2564void
2565rec_offs_make_nth_extern(
2566 ulint* offsets,
2567 const ulint n)
2568{
2569 ut_ad(!rec_offs_nth_sql_null(offsets, n));
2570 rec_offs_base(offsets)[1 + n] |= REC_OFFS_EXTERNAL;
2571}
2572#ifdef WITH_WSREP
2573int
2574wsrep_rec_get_foreign_key(
2575 byte *buf, /* out: extracted key */
2576 ulint *buf_len, /* in/out: length of buf */
2577 const rec_t* rec, /* in: physical record */
2578 dict_index_t* index_for, /* in: index in foreign table */
2579 dict_index_t* index_ref, /* in: index in referenced table */
2580 ibool new_protocol) /* in: protocol > 1 */
2581{
2582 const byte* data;
2583 ulint len;
2584 ulint key_len = 0;
2585 ulint i;
2586 uint key_parts;
2587 mem_heap_t* heap = NULL;
2588 ulint offsets_[REC_OFFS_NORMAL_SIZE];
2589 const ulint* offsets;
2590
2591 ut_ad(index_for);
2592 ut_ad(index_ref);
2593
2594 rec_offs_init(offsets_);
2595 offsets = rec_get_offsets(rec, index_for, offsets_, true,
2596 ULINT_UNDEFINED, &heap);
2597
2598 ut_ad(rec_offs_validate(rec, NULL, offsets));
2599
2600 ut_ad(rec);
2601
2602 key_parts = dict_index_get_n_unique_in_tree(index_for);
2603 for (i = 0;
2604 i < key_parts &&
2605 (index_for->type & DICT_CLUSTERED || i < key_parts - 1);
2606 i++) {
2607 dict_field_t* field_f =
2608 dict_index_get_nth_field(index_for, i);
2609 const dict_col_t* col_f = dict_field_get_col(field_f);
2610 dict_field_t* field_r =
2611 dict_index_get_nth_field(index_ref, i);
2612 const dict_col_t* col_r = dict_field_get_col(field_r);
2613
2614 ut_ad(!rec_offs_nth_default(offsets, i));
2615 data = rec_get_nth_field(rec, offsets, i, &len);
2616 if (key_len + ((len != UNIV_SQL_NULL) ? len + 1 : 1) >
2617 *buf_len) {
2618 fprintf(stderr,
2619 "WSREP: FK key len exceeded "
2620 ULINTPF " " ULINTPF " " ULINTPF "\n",
2621 key_len, len, *buf_len);
2622 goto err_out;
2623 }
2624
2625 if (len == UNIV_SQL_NULL) {
2626 ut_a(!(col_f->prtype & DATA_NOT_NULL));
2627 *buf++ = 1;
2628 key_len++;
2629 } else if (!new_protocol) {
2630 if (!(col_r->prtype & DATA_NOT_NULL)) {
2631 *buf++ = 0;
2632 key_len++;
2633 }
2634 memcpy(buf, data, len);
2635 *buf_len = wsrep_innobase_mysql_sort(
2636 (int)(col_f->prtype & DATA_MYSQL_TYPE_MASK),
2637 (uint)dtype_get_charset_coll(col_f->prtype),
2638 buf, len, *buf_len);
2639 } else { /* new protocol */
2640 if (!(col_r->prtype & DATA_NOT_NULL)) {
2641 *buf++ = 0;
2642 key_len++;
2643 }
2644 switch (col_f->mtype) {
2645 case DATA_INT: {
2646 byte* ptr = buf+len;
2647 for (;;) {
2648 ptr--;
2649 *ptr = *data;
2650 if (ptr == buf) {
2651 break;
2652 }
2653 data++;
2654 }
2655
2656 if (!(col_f->prtype & DATA_UNSIGNED)) {
2657 buf[len-1] = (byte) (buf[len-1] ^ 128);
2658 }
2659
2660 break;
2661 }
2662 case DATA_VARCHAR:
2663 case DATA_VARMYSQL:
2664 case DATA_CHAR:
2665 case DATA_MYSQL:
2666 /* Copy the actual data */
2667 ut_memcpy(buf, data, len);
2668 len = wsrep_innobase_mysql_sort(
2669 (int)
2670 (col_f->prtype & DATA_MYSQL_TYPE_MASK),
2671 (uint)
2672 dtype_get_charset_coll(col_f->prtype),
2673 buf, len, *buf_len);
2674 break;
2675 case DATA_BLOB:
2676 case DATA_BINARY:
2677 memcpy(buf, data, len);
2678 break;
2679 default:
2680 break;
2681 }
2682
2683 key_len += len;
2684 buf += len;
2685 }
2686 }
2687
2688 rec_validate(rec, offsets);
2689
2690 if (UNIV_LIKELY_NULL(heap)) {
2691 mem_heap_free(heap);
2692 }
2693
2694 *buf_len = key_len;
2695 return DB_SUCCESS;
2696
2697 err_out:
2698 if (UNIV_LIKELY_NULL(heap)) {
2699 mem_heap_free(heap);
2700 }
2701 return DB_ERROR;
2702}
2703#endif // WITH_WSREP
2704