1 /*****************************************************************************
2
3 Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License, version 2.0,
7 as published by the Free Software Foundation.
8
9 This program is also distributed with certain software (including
10 but not limited to OpenSSL) that is licensed under separate terms,
11 as designated in a particular file or component or in included license
12 documentation. The authors of MySQL hereby grant you an additional
13 permission to link the program and your derivative works with the
14 separately licensed software that they have included with MySQL.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License, version 2.0, for more details.
20
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
24
25 *****************************************************************************/
26
27 /********************************************************************//**
28 @file data/data0data.cc
29 SQL data field and tuple
30
31 Created 5/30/1994 Heikki Tuuri
32 *************************************************************************/
33
34 #include "data0data.h"
35
36 #ifdef UNIV_NONINL
37 #include "data0data.ic"
38 #endif
39
40 #ifndef UNIV_HOTBACKUP
41 #include "rem0rec.h"
42 #include "rem0cmp.h"
43 #include "page0page.h"
44 #include "page0zip.h"
45 #include "dict0dict.h"
46 #include "btr0cur.h"
47
48 #include <ctype.h>
49 #endif /* !UNIV_HOTBACKUP */
50
51 #ifdef UNIV_DEBUG
52 /** Dummy variable to catch access to uninitialized fields. In the
53 debug version, dtuple_create() will make all fields of dtuple_t point
54 to data_error. */
55 UNIV_INTERN byte data_error;
56
57 # ifndef UNIV_DEBUG_VALGRIND
58 /** this is used to fool the compiler in dtuple_validate */
59 UNIV_INTERN ulint data_dummy;
60 # endif /* !UNIV_DEBUG_VALGRIND */
61 #endif /* UNIV_DEBUG */
62
63 #ifndef UNIV_HOTBACKUP
64 /************************************************************//**
65 Compare two data tuples, respecting the collation of character fields.
66 @return 1, 0 , -1 if tuple1 is greater, equal, less, respectively,
67 than tuple2 */
68 UNIV_INTERN
69 int
dtuple_coll_cmp(const dtuple_t * tuple1,const dtuple_t * tuple2)70 dtuple_coll_cmp(
71 /*============*/
72 const dtuple_t* tuple1, /*!< in: tuple 1 */
73 const dtuple_t* tuple2) /*!< in: tuple 2 */
74 {
75 ulint n_fields;
76 ulint i;
77
78 ut_ad(tuple1 != NULL);
79 ut_ad(tuple2 != NULL);
80 ut_ad(tuple1->magic_n == DATA_TUPLE_MAGIC_N);
81 ut_ad(tuple2->magic_n == DATA_TUPLE_MAGIC_N);
82 ut_ad(dtuple_check_typed(tuple1));
83 ut_ad(dtuple_check_typed(tuple2));
84
85 n_fields = dtuple_get_n_fields(tuple1);
86
87 if (n_fields != dtuple_get_n_fields(tuple2)) {
88
89 return(n_fields < dtuple_get_n_fields(tuple2) ? -1 : 1);
90 }
91
92 for (i = 0; i < n_fields; i++) {
93 int cmp;
94 const dfield_t* field1 = dtuple_get_nth_field(tuple1, i);
95 const dfield_t* field2 = dtuple_get_nth_field(tuple2, i);
96
97 cmp = cmp_dfield_dfield(field1, field2);
98
99 if (cmp) {
100 return(cmp);
101 }
102 }
103
104 return(0);
105 }
106
107 /*********************************************************************//**
108 Sets number of fields used in a tuple. Normally this is set in
109 dtuple_create, but if you want later to set it smaller, you can use this. */
110 UNIV_INTERN
111 void
dtuple_set_n_fields(dtuple_t * tuple,ulint n_fields)112 dtuple_set_n_fields(
113 /*================*/
114 dtuple_t* tuple, /*!< in: tuple */
115 ulint n_fields) /*!< in: number of fields */
116 {
117 ut_ad(tuple);
118
119 tuple->n_fields = n_fields;
120 tuple->n_fields_cmp = n_fields;
121 }
122
123 /**********************************************************//**
124 Checks that a data field is typed.
125 @return TRUE if ok */
126 static
127 ibool
dfield_check_typed_no_assert(const dfield_t * field)128 dfield_check_typed_no_assert(
129 /*=========================*/
130 const dfield_t* field) /*!< in: data field */
131 {
132 if (dfield_get_type(field)->mtype > DATA_MYSQL
133 || dfield_get_type(field)->mtype < DATA_VARCHAR) {
134
135 fprintf(stderr,
136 "InnoDB: Error: data field type %lu, len %lu\n",
137 (ulong) dfield_get_type(field)->mtype,
138 (ulong) dfield_get_len(field));
139 return(FALSE);
140 }
141
142 return(TRUE);
143 }
144
145 /**********************************************************//**
146 Checks that a data tuple is typed.
147 @return TRUE if ok */
148 UNIV_INTERN
149 ibool
dtuple_check_typed_no_assert(const dtuple_t * tuple)150 dtuple_check_typed_no_assert(
151 /*=========================*/
152 const dtuple_t* tuple) /*!< in: tuple */
153 {
154 const dfield_t* field;
155 ulint i;
156
157 if (dtuple_get_n_fields(tuple) > REC_MAX_N_FIELDS) {
158 fprintf(stderr,
159 "InnoDB: Error: index entry has %lu fields\n",
160 (ulong) dtuple_get_n_fields(tuple));
161 dump:
162 fputs("InnoDB: Tuple contents: ", stderr);
163 dtuple_print(stderr, tuple);
164 putc('\n', stderr);
165
166 return(FALSE);
167 }
168
169 for (i = 0; i < dtuple_get_n_fields(tuple); i++) {
170
171 field = dtuple_get_nth_field(tuple, i);
172
173 if (!dfield_check_typed_no_assert(field)) {
174 goto dump;
175 }
176 }
177
178 return(TRUE);
179 }
180 #endif /* !UNIV_HOTBACKUP */
181
182 #ifdef UNIV_DEBUG
183 /**********************************************************//**
184 Checks that a data field is typed. Asserts an error if not.
185 @return TRUE if ok */
186 UNIV_INTERN
187 ibool
dfield_check_typed(const dfield_t * field)188 dfield_check_typed(
189 /*===============*/
190 const dfield_t* field) /*!< in: data field */
191 {
192 if (dfield_get_type(field)->mtype > DATA_MYSQL
193 || dfield_get_type(field)->mtype < DATA_VARCHAR) {
194
195 fprintf(stderr,
196 "InnoDB: Error: data field type %lu, len %lu\n",
197 (ulong) dfield_get_type(field)->mtype,
198 (ulong) dfield_get_len(field));
199
200 ut_error;
201 }
202
203 return(TRUE);
204 }
205
206 /**********************************************************//**
207 Checks that a data tuple is typed. Asserts an error if not.
208 @return TRUE if ok */
209 UNIV_INTERN
210 ibool
dtuple_check_typed(const dtuple_t * tuple)211 dtuple_check_typed(
212 /*===============*/
213 const dtuple_t* tuple) /*!< in: tuple */
214 {
215 const dfield_t* field;
216 ulint i;
217
218 for (i = 0; i < dtuple_get_n_fields(tuple); i++) {
219
220 field = dtuple_get_nth_field(tuple, i);
221
222 ut_a(dfield_check_typed(field));
223 }
224
225 return(TRUE);
226 }
227
228 /**********************************************************//**
229 Validates the consistency of a tuple which must be complete, i.e,
230 all fields must have been set.
231 @return TRUE if ok */
232 UNIV_INTERN
233 ibool
dtuple_validate(const dtuple_t * tuple)234 dtuple_validate(
235 /*============*/
236 const dtuple_t* tuple) /*!< in: tuple */
237 {
238 const dfield_t* field;
239 ulint n_fields;
240 ulint len;
241 ulint i;
242
243 ut_ad(tuple->magic_n == DATA_TUPLE_MAGIC_N);
244
245 n_fields = dtuple_get_n_fields(tuple);
246
247 /* We dereference all the data of each field to test
248 for memory traps */
249
250 for (i = 0; i < n_fields; i++) {
251
252 field = dtuple_get_nth_field(tuple, i);
253 len = dfield_get_len(field);
254
255 if (!dfield_is_null(field)) {
256
257 const byte* data;
258
259 data = static_cast<const byte*>(dfield_get_data(field));
260 #ifndef UNIV_DEBUG_VALGRIND
261 ulint j;
262
263 for (j = 0; j < len; j++) {
264
265 data_dummy += *data; /* fool the compiler not
266 to optimize out this
267 code */
268 data++;
269 }
270 #endif /* !UNIV_DEBUG_VALGRIND */
271
272 UNIV_MEM_ASSERT_RW(data, len);
273 }
274 }
275
276 ut_a(dtuple_check_typed(tuple));
277
278 return(TRUE);
279 }
280 #endif /* UNIV_DEBUG */
281
282 #ifndef UNIV_HOTBACKUP
283 /*************************************************************//**
284 Pretty prints a dfield value according to its data type. */
285 UNIV_INTERN
286 void
dfield_print(const dfield_t * dfield)287 dfield_print(
288 /*=========*/
289 const dfield_t* dfield) /*!< in: dfield */
290 {
291 const byte* data;
292 ulint len;
293 ulint i;
294
295 len = dfield_get_len(dfield);
296 data = static_cast<const byte*>(dfield_get_data(dfield));
297
298 if (dfield_is_null(dfield)) {
299 fputs("NULL", stderr);
300
301 return;
302 }
303
304 switch (dtype_get_mtype(dfield_get_type(dfield))) {
305 case DATA_CHAR:
306 case DATA_VARCHAR:
307 for (i = 0; i < len; i++) {
308 int c = *data++;
309 putc(isprint(c) ? c : ' ', stderr);
310 }
311
312 if (dfield_is_ext(dfield)) {
313 fputs("(external)", stderr);
314 }
315 break;
316 case DATA_INT:
317 ut_a(len == 4); /* only works for 32-bit integers */
318 fprintf(stderr, "%d", (int) mach_read_from_4(data));
319 break;
320 default:
321 ut_error;
322 }
323 }
324
325 /*************************************************************//**
326 Pretty prints a dfield value according to its data type. Also the hex string
327 is printed if a string contains non-printable characters. */
328 UNIV_INTERN
329 void
dfield_print_also_hex(const dfield_t * dfield)330 dfield_print_also_hex(
331 /*==================*/
332 const dfield_t* dfield) /*!< in: dfield */
333 {
334 const byte* data;
335 ulint len;
336 ulint prtype;
337 ulint i;
338 ibool print_also_hex;
339
340 len = dfield_get_len(dfield);
341 data = static_cast<const byte*>(dfield_get_data(dfield));
342
343 if (dfield_is_null(dfield)) {
344 fputs("NULL", stderr);
345
346 return;
347 }
348
349 prtype = dtype_get_prtype(dfield_get_type(dfield));
350
351 switch (dtype_get_mtype(dfield_get_type(dfield))) {
352 ib_id_t id;
353 case DATA_INT:
354 switch (len) {
355 ulint val;
356 case 1:
357 val = mach_read_from_1(data);
358
359 if (!(prtype & DATA_UNSIGNED)) {
360 val &= ~0x80;
361 fprintf(stderr, "%ld", (long) val);
362 } else {
363 fprintf(stderr, "%lu", (ulong) val);
364 }
365 break;
366
367 case 2:
368 val = mach_read_from_2(data);
369
370 if (!(prtype & DATA_UNSIGNED)) {
371 val &= ~0x8000;
372 fprintf(stderr, "%ld", (long) val);
373 } else {
374 fprintf(stderr, "%lu", (ulong) val);
375 }
376 break;
377
378 case 3:
379 val = mach_read_from_3(data);
380
381 if (!(prtype & DATA_UNSIGNED)) {
382 val &= ~0x800000;
383 fprintf(stderr, "%ld", (long) val);
384 } else {
385 fprintf(stderr, "%lu", (ulong) val);
386 }
387 break;
388
389 case 4:
390 val = mach_read_from_4(data);
391
392 if (!(prtype & DATA_UNSIGNED)) {
393 val &= ~0x80000000;
394 fprintf(stderr, "%ld", (long) val);
395 } else {
396 fprintf(stderr, "%lu", (ulong) val);
397 }
398 break;
399
400 case 6:
401 id = mach_read_from_6(data);
402 fprintf(stderr, "%llu", (ullint) id);
403 break;
404
405 case 7:
406 id = mach_read_from_7(data);
407 fprintf(stderr, "%llu", (ullint) id);
408 break;
409 case 8:
410 id = mach_read_from_8(data);
411 fprintf(stderr, "%llu", (ullint) id);
412 break;
413 default:
414 goto print_hex;
415 }
416 break;
417
418 case DATA_SYS:
419 switch (prtype & DATA_SYS_PRTYPE_MASK) {
420 case DATA_TRX_ID:
421 id = mach_read_from_6(data);
422
423 fprintf(stderr, "trx_id " TRX_ID_FMT, id);
424 break;
425
426 case DATA_ROLL_PTR:
427 id = mach_read_from_7(data);
428
429 fprintf(stderr, "roll_ptr " TRX_ID_FMT, id);
430 break;
431
432 case DATA_ROW_ID:
433 id = mach_read_from_6(data);
434
435 fprintf(stderr, "row_id " TRX_ID_FMT, id);
436 break;
437
438 default:
439 id = mach_ull_read_compressed(data);
440
441 fprintf(stderr, "mix_id " TRX_ID_FMT, id);
442 }
443 break;
444
445 case DATA_CHAR:
446 case DATA_VARCHAR:
447 print_also_hex = FALSE;
448
449 for (i = 0; i < len; i++) {
450 int c = *data++;
451
452 if (!isprint(c)) {
453 print_also_hex = TRUE;
454
455 fprintf(stderr, "\\x%02x", (unsigned char) c);
456 } else {
457 putc(c, stderr);
458 }
459 }
460
461 if (dfield_is_ext(dfield)) {
462 fputs("(external)", stderr);
463 }
464
465 if (!print_also_hex) {
466 break;
467 }
468
469 data = static_cast<byte*>(dfield_get_data(dfield));
470 /* fall through */
471
472 case DATA_BINARY:
473 default:
474 print_hex:
475 fputs(" Hex: ",stderr);
476
477 for (i = 0; i < len; i++) {
478 fprintf(stderr, "%02lx", (ulint) *data++);
479 }
480
481 if (dfield_is_ext(dfield)) {
482 fputs("(external)", stderr);
483 }
484 }
485 }
486
487 /*************************************************************//**
488 Print a dfield value using ut_print_buf. */
489 static
490 void
dfield_print_raw(FILE * f,const dfield_t * dfield)491 dfield_print_raw(
492 /*=============*/
493 FILE* f, /*!< in: output stream */
494 const dfield_t* dfield) /*!< in: dfield */
495 {
496 ulint len = dfield_get_len(dfield);
497 if (!dfield_is_null(dfield)) {
498 ulint print_len = ut_min(len, 1000);
499 ut_print_buf(f, dfield_get_data(dfield), print_len);
500 if (len != print_len) {
501 fprintf(f, "(total %lu bytes%s)",
502 (ulong) len,
503 dfield_is_ext(dfield) ? ", external" : "");
504 }
505 } else {
506 fputs(" SQL NULL", f);
507 }
508 }
509
510 /**********************************************************//**
511 The following function prints the contents of a tuple. */
512 UNIV_INTERN
513 void
dtuple_print(FILE * f,const dtuple_t * tuple)514 dtuple_print(
515 /*=========*/
516 FILE* f, /*!< in: output stream */
517 const dtuple_t* tuple) /*!< in: tuple */
518 {
519 ulint n_fields;
520 ulint i;
521
522 n_fields = dtuple_get_n_fields(tuple);
523
524 fprintf(f, "DATA TUPLE: %lu fields;\n", (ulong) n_fields);
525
526 for (i = 0; i < n_fields; i++) {
527 fprintf(f, " %lu:", (ulong) i);
528
529 dfield_print_raw(f, dtuple_get_nth_field(tuple, i));
530
531 putc(';', f);
532 putc('\n', f);
533 }
534
535 ut_ad(dtuple_validate(tuple));
536 }
537
538 /**************************************************************//**
539 Moves parts of long fields in entry to the big record vector so that
540 the size of tuple drops below the maximum record size allowed in the
541 database. Moves data only from those fields which are not necessary
542 to determine uniquely the insertion place of the tuple in the index.
543 @return own: created big record vector, NULL if we are not able to
544 shorten the entry enough, i.e., if there are too many fixed-length or
545 short fields in entry or the index is clustered */
546 UNIV_INTERN
547 big_rec_t*
dtuple_convert_big_rec(dict_index_t * index,dtuple_t * entry,ulint * n_ext)548 dtuple_convert_big_rec(
549 /*===================*/
550 dict_index_t* index, /*!< in: index */
551 dtuple_t* entry, /*!< in/out: index entry */
552 ulint* n_ext) /*!< in/out: number of
553 externally stored columns */
554 {
555 mem_heap_t* heap;
556 big_rec_t* vector;
557 dfield_t* dfield;
558 dict_field_t* ifield;
559 ulint size;
560 ulint n_fields;
561 ulint local_len;
562 ulint local_prefix_len;
563
564 if (!dict_index_is_clust(index)) {
565 return(NULL);
566 }
567
568 if (dict_table_get_format(index->table) < UNIV_FORMAT_B) {
569 /* up to MySQL 5.1: store a 768-byte prefix locally */
570 local_len = BTR_EXTERN_FIELD_REF_SIZE
571 + DICT_ANTELOPE_MAX_INDEX_COL_LEN;
572 } else {
573 /* new-format table: do not store any BLOB prefix locally */
574 local_len = BTR_EXTERN_FIELD_REF_SIZE;
575 }
576
577 ut_a(dtuple_check_typed_no_assert(entry));
578
579 size = rec_get_converted_size(index, entry, *n_ext);
580
581 if (UNIV_UNLIKELY(size > 1000000000)) {
582 fprintf(stderr,
583 "InnoDB: Warning: tuple size very big: %lu\n",
584 (ulong) size);
585 fputs("InnoDB: Tuple contents: ", stderr);
586 dtuple_print(stderr, entry);
587 putc('\n', stderr);
588 }
589
590 heap = mem_heap_create(size + dtuple_get_n_fields(entry)
591 * sizeof(big_rec_field_t) + 1000);
592
593 vector = static_cast<big_rec_t*>(
594 mem_heap_alloc(heap, sizeof(big_rec_t)));
595
596 vector->heap = heap;
597
598 vector->fields = static_cast<big_rec_field_t*>(
599 mem_heap_alloc(
600 heap,
601 dtuple_get_n_fields(entry) * sizeof(big_rec_field_t)));
602
603 /* Decide which fields to shorten: the algorithm is to look for
604 a variable-length field that yields the biggest savings when
605 stored externally */
606
607 n_fields = 0;
608
609 while (page_zip_rec_needs_ext(rec_get_converted_size(index, entry,
610 *n_ext),
611 dict_table_is_comp(index->table),
612 dict_index_get_n_fields(index),
613 dict_table_zip_size(index->table))) {
614 ulint i;
615 ulint longest = 0;
616 ulint longest_i = ULINT_MAX;
617 byte* data;
618 big_rec_field_t* b;
619
620 for (i = dict_index_get_n_unique_in_tree(index);
621 i < dtuple_get_n_fields(entry); i++) {
622 ulint savings;
623
624 dfield = dtuple_get_nth_field(entry, i);
625 ifield = dict_index_get_nth_field(index, i);
626
627 /* Skip fixed-length, NULL, externally stored,
628 or short columns */
629
630 if (ifield->fixed_len
631 || dfield_is_null(dfield)
632 || dfield_is_ext(dfield)
633 || dfield_get_len(dfield) <= local_len
634 || dfield_get_len(dfield)
635 <= BTR_EXTERN_FIELD_REF_SIZE * 2) {
636 goto skip_field;
637 }
638
639 savings = dfield_get_len(dfield) - local_len;
640
641 /* Check that there would be savings */
642 if (longest >= savings) {
643 goto skip_field;
644 }
645
646 /* In DYNAMIC and COMPRESSED format, store
647 locally any non-BLOB columns whose maximum
648 length does not exceed 256 bytes. This is
649 because there is no room for the "external
650 storage" flag when the maximum length is 255
651 bytes or less. This restriction trivially
652 holds in REDUNDANT and COMPACT format, because
653 there we always store locally columns whose
654 length is up to local_len == 788 bytes.
655 @see rec_init_offsets_comp_ordinary */
656 if (ifield->col->mtype != DATA_BLOB
657 && ifield->col->len < 256) {
658 goto skip_field;
659 }
660
661 longest_i = i;
662 longest = savings;
663
664 skip_field:
665 continue;
666 }
667
668 if (!longest) {
669 /* Cannot shorten more */
670
671 mem_heap_free(heap);
672
673 return(NULL);
674 }
675
676 /* Move data from field longest_i to big rec vector.
677
678 We store the first bytes locally to the record. Then
679 we can calculate all ordering fields in all indexes
680 from locally stored data. */
681
682 dfield = dtuple_get_nth_field(entry, longest_i);
683 ifield = dict_index_get_nth_field(index, longest_i);
684 local_prefix_len = local_len - BTR_EXTERN_FIELD_REF_SIZE;
685
686 b = &vector->fields[n_fields];
687 b->field_no = longest_i;
688 b->len = dfield_get_len(dfield) - local_prefix_len;
689 b->data = (char*) dfield_get_data(dfield) + local_prefix_len;
690
691 /* Allocate the locally stored part of the column. */
692 data = static_cast<byte*>(mem_heap_alloc(heap, local_len));
693
694 /* Copy the local prefix. */
695 memcpy(data, dfield_get_data(dfield), local_prefix_len);
696 /* Clear the extern field reference (BLOB pointer). */
697 memset(data + local_prefix_len, 0, BTR_EXTERN_FIELD_REF_SIZE);
698 #if 0
699 /* The following would fail the Valgrind checks in
700 page_cur_insert_rec_low() and page_cur_insert_rec_zip().
701 The BLOB pointers in the record will be initialized after
702 the record and the BLOBs have been written. */
703 UNIV_MEM_ALLOC(data + local_prefix_len,
704 BTR_EXTERN_FIELD_REF_SIZE);
705 #endif
706
707 dfield_set_data(dfield, data, local_len);
708 dfield_set_ext(dfield);
709
710 n_fields++;
711 (*n_ext)++;
712 ut_ad(n_fields < dtuple_get_n_fields(entry));
713 }
714
715 vector->n_fields = n_fields;
716 return(vector);
717 }
718
719 /**************************************************************//**
720 Puts back to entry the data stored in vector. Note that to ensure the
721 fields in entry can accommodate the data, vector must have been created
722 from entry with dtuple_convert_big_rec. */
723 UNIV_INTERN
724 void
dtuple_convert_back_big_rec(dict_index_t * index MY_ATTRIBUTE ((unused)),dtuple_t * entry,big_rec_t * vector)725 dtuple_convert_back_big_rec(
726 /*========================*/
727 dict_index_t* index MY_ATTRIBUTE((unused)), /*!< in: index */
728 dtuple_t* entry, /*!< in: entry whose data was put to vector */
729 big_rec_t* vector) /*!< in, own: big rec vector; it is
730 freed in this function */
731 {
732 big_rec_field_t* b = vector->fields;
733 const big_rec_field_t* const end = b + vector->n_fields;
734
735 for (; b < end; b++) {
736 dfield_t* dfield;
737 ulint local_len;
738
739 dfield = dtuple_get_nth_field(entry, b->field_no);
740 local_len = dfield_get_len(dfield);
741
742 ut_ad(dfield_is_ext(dfield));
743 ut_ad(local_len >= BTR_EXTERN_FIELD_REF_SIZE);
744
745 local_len -= BTR_EXTERN_FIELD_REF_SIZE;
746
747 /* Only in REDUNDANT and COMPACT format, we store
748 up to DICT_ANTELOPE_MAX_INDEX_COL_LEN (768) bytes
749 locally */
750 ut_ad(local_len <= DICT_ANTELOPE_MAX_INDEX_COL_LEN);
751
752 dfield_set_data(dfield,
753 (char*) b->data - local_len,
754 b->len + local_len);
755 }
756
757 mem_heap_free(vector->heap);
758 }
759 #endif /* !UNIV_HOTBACKUP */
760