1 /***************************************************************************** 2 3 Copyright (c) 1996, 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 include/btr0pcur.h 29 The index tree persistent cursor 30 31 Created 2/23/1996 Heikki Tuuri 32 *******************************************************/ 33 34 #ifndef btr0pcur_h 35 #define btr0pcur_h 36 37 #include "univ.i" 38 #include "dict0dict.h" 39 #include "data0data.h" 40 #include "mtr0mtr.h" 41 #include "page0cur.h" 42 #include "btr0cur.h" 43 #include "btr0btr.h" 44 #include "btr0types.h" 45 46 /* Relative positions for a stored cursor position */ 47 #define BTR_PCUR_ON 1 48 #define BTR_PCUR_BEFORE 2 49 #define BTR_PCUR_AFTER 3 50 /* Note that if the tree is not empty, btr_pcur_store_position does not 51 use the following, but only uses the above three alternatives, where the 52 position is stored relative to a specific record: this makes implementation 53 of a scroll cursor easier */ 54 #define BTR_PCUR_BEFORE_FIRST_IN_TREE 4 /* in an empty tree */ 55 #define BTR_PCUR_AFTER_LAST_IN_TREE 5 /* in an empty tree */ 56 57 /**************************************************************//** 58 Allocates memory for a persistent cursor object and initializes the cursor. 59 @return own: persistent cursor */ 60 UNIV_INTERN 61 btr_pcur_t* 62 btr_pcur_create_for_mysql(void); 63 /*============================*/ 64 65 /**************************************************************//** 66 Resets a persistent cursor object, freeing ::old_rec_buf if it is 67 allocated and resetting the other members to their initial values. */ 68 UNIV_INTERN 69 void 70 btr_pcur_reset( 71 /*===========*/ 72 btr_pcur_t* cursor);/*!< in, out: persistent cursor */ 73 74 /**************************************************************//** 75 Frees the memory for a persistent cursor object. */ 76 UNIV_INTERN 77 void 78 btr_pcur_free_for_mysql( 79 /*====================*/ 80 btr_pcur_t* cursor); /*!< in, own: persistent cursor */ 81 /**************************************************************//** 82 Copies the stored position of a pcur to another pcur. */ 83 UNIV_INTERN 84 void 85 btr_pcur_copy_stored_position( 86 /*==========================*/ 87 btr_pcur_t* pcur_receive, /*!< in: pcur which will receive the 88 position info */ 89 btr_pcur_t* pcur_donate); /*!< in: pcur from which the info is 90 copied */ 91 /**************************************************************//** 92 Sets the old_rec_buf field to NULL. */ 93 UNIV_INLINE 94 void 95 btr_pcur_init( 96 /*==========*/ 97 btr_pcur_t* pcur); /*!< in: persistent cursor */ 98 /**************************************************************//** 99 Initializes and opens a persistent cursor to an index tree. It should be 100 closed with btr_pcur_close. */ 101 UNIV_INLINE 102 void 103 btr_pcur_open_low( 104 /*==============*/ 105 dict_index_t* index, /*!< in: index */ 106 ulint level, /*!< in: level in the btree */ 107 const dtuple_t* tuple, /*!< in: tuple on which search done */ 108 ulint mode, /*!< in: PAGE_CUR_L, ...; 109 NOTE that if the search is made using a unique 110 prefix of a record, mode should be 111 PAGE_CUR_LE, not PAGE_CUR_GE, as the latter 112 may end up on the previous page from the 113 record! */ 114 ulint latch_mode,/*!< in: BTR_SEARCH_LEAF, ... */ 115 btr_pcur_t* cursor, /*!< in: memory buffer for persistent cursor */ 116 const char* file, /*!< in: file name */ 117 ulint line, /*!< in: line where called */ 118 mtr_t* mtr); /*!< in: mtr */ 119 #define btr_pcur_open(i,t,md,l,c,m) \ 120 btr_pcur_open_low(i,0,t,md,l,c,__FILE__,__LINE__,m) 121 /**************************************************************//** 122 Opens an persistent cursor to an index tree without initializing the 123 cursor. */ 124 UNIV_INLINE 125 void 126 btr_pcur_open_with_no_init_func( 127 /*============================*/ 128 dict_index_t* index, /*!< in: index */ 129 const dtuple_t* tuple, /*!< in: tuple on which search done */ 130 ulint mode, /*!< in: PAGE_CUR_L, ...; 131 NOTE that if the search is made using a unique 132 prefix of a record, mode should be 133 PAGE_CUR_LE, not PAGE_CUR_GE, as the latter 134 may end up on the previous page of the 135 record! */ 136 ulint latch_mode,/*!< in: BTR_SEARCH_LEAF, ...; 137 NOTE that if has_search_latch != 0 then 138 we maybe do not acquire a latch on the cursor 139 page, but assume that the caller uses his 140 btr search latch to protect the record! */ 141 btr_pcur_t* cursor, /*!< in: memory buffer for persistent cursor */ 142 ulint has_search_latch,/*!< in: latch mode the caller 143 currently has on btr_search_latch: 144 RW_S_LATCH, or 0 */ 145 const char* file, /*!< in: file name */ 146 ulint line, /*!< in: line where called */ 147 mtr_t* mtr); /*!< in: mtr */ 148 #define btr_pcur_open_with_no_init(ix,t,md,l,cur,has,m) \ 149 btr_pcur_open_with_no_init_func(ix,t,md,l,cur,has,__FILE__,__LINE__,m) 150 151 /*****************************************************************//** 152 Opens a persistent cursor at either end of an index. */ 153 UNIV_INLINE 154 void 155 btr_pcur_open_at_index_side( 156 /*========================*/ 157 bool from_left, /*!< in: true if open to the low end, 158 false if to the high end */ 159 dict_index_t* index, /*!< in: index */ 160 ulint latch_mode, /*!< in: latch mode */ 161 btr_pcur_t* pcur, /*!< in/out: cursor */ 162 bool init_pcur, /*!< in: whether to initialize pcur */ 163 ulint level, /*!< in: level to search for 164 (0=leaf) */ 165 mtr_t* mtr) /*!< in/out: mini-transaction */ 166 MY_ATTRIBUTE((nonnull)); 167 /**************************************************************//** 168 Gets the up_match value for a pcur after a search. 169 @return number of matched fields at the cursor or to the right if 170 search mode was PAGE_CUR_GE, otherwise undefined */ 171 UNIV_INLINE 172 ulint 173 btr_pcur_get_up_match( 174 /*==================*/ 175 const btr_pcur_t* cursor); /*!< in: persistent cursor */ 176 /**************************************************************//** 177 Gets the low_match value for a pcur after a search. 178 @return number of matched fields at the cursor or to the right if 179 search mode was PAGE_CUR_LE, otherwise undefined */ 180 UNIV_INLINE 181 ulint 182 btr_pcur_get_low_match( 183 /*===================*/ 184 const btr_pcur_t* cursor); /*!< in: persistent cursor */ 185 /**************************************************************//** 186 If mode is PAGE_CUR_G or PAGE_CUR_GE, opens a persistent cursor on the first 187 user record satisfying the search condition, in the case PAGE_CUR_L or 188 PAGE_CUR_LE, on the last user record. If no such user record exists, then 189 in the first case sets the cursor after last in tree, and in the latter case 190 before first in tree. The latching mode must be BTR_SEARCH_LEAF or 191 BTR_MODIFY_LEAF. */ 192 UNIV_INTERN 193 void 194 btr_pcur_open_on_user_rec_func( 195 /*===========================*/ 196 dict_index_t* index, /*!< in: index */ 197 const dtuple_t* tuple, /*!< in: tuple on which search done */ 198 ulint mode, /*!< in: PAGE_CUR_L, ... */ 199 ulint latch_mode, /*!< in: BTR_SEARCH_LEAF or 200 BTR_MODIFY_LEAF */ 201 btr_pcur_t* cursor, /*!< in: memory buffer for persistent 202 cursor */ 203 const char* file, /*!< in: file name */ 204 ulint line, /*!< in: line where called */ 205 mtr_t* mtr); /*!< in: mtr */ 206 #define btr_pcur_open_on_user_rec(i,t,md,l,c,m) \ 207 btr_pcur_open_on_user_rec_func(i,t,md,l,c,__FILE__,__LINE__,m) 208 /**********************************************************************//** 209 Positions a cursor at a randomly chosen position within a B-tree. */ 210 UNIV_INLINE 211 void 212 btr_pcur_open_at_rnd_pos_func( 213 /*==========================*/ 214 dict_index_t* index, /*!< in: index */ 215 ulint latch_mode, /*!< in: BTR_SEARCH_LEAF, ... */ 216 btr_pcur_t* cursor, /*!< in/out: B-tree pcur */ 217 const char* file, /*!< in: file name */ 218 ulint line, /*!< in: line where called */ 219 mtr_t* mtr); /*!< in: mtr */ 220 #define btr_pcur_open_at_rnd_pos(i,l,c,m) \ 221 btr_pcur_open_at_rnd_pos_func(i,l,c,__FILE__,__LINE__,m) 222 /**************************************************************//** 223 Frees the possible memory heap of a persistent cursor and sets the latch 224 mode of the persistent cursor to BTR_NO_LATCHES. 225 WARNING: this function does not release the latch on the page where the 226 cursor is currently positioned. The latch is acquired by the 227 "move to next/previous" family of functions. Since recursive shared locks 228 are not allowed, you must take care (if using the cursor in S-mode) to 229 manually release the latch by either calling 230 btr_leaf_page_release(btr_pcur_get_block(&pcur), pcur.latch_mode, mtr) 231 or by committing the mini-transaction right after btr_pcur_close(). 232 A subsequent attempt to crawl the same page in the same mtr would cause 233 an assertion failure. */ 234 UNIV_INLINE 235 void 236 btr_pcur_close( 237 /*===========*/ 238 btr_pcur_t* cursor); /*!< in: persistent cursor */ 239 /**************************************************************//** 240 The position of the cursor is stored by taking an initial segment of the 241 record the cursor is positioned on, before, or after, and copying it to the 242 cursor data structure, or just setting a flag if the cursor id before the 243 first in an EMPTY tree, or after the last in an EMPTY tree. NOTE that the 244 page where the cursor is positioned must not be empty if the index tree is 245 not totally empty! */ 246 UNIV_INTERN 247 void 248 btr_pcur_store_position( 249 /*====================*/ 250 btr_pcur_t* cursor, /*!< in: persistent cursor */ 251 mtr_t* mtr); /*!< in: mtr */ 252 /**************************************************************//** 253 Restores the stored position of a persistent cursor bufferfixing the page and 254 obtaining the specified latches. If the cursor position was saved when the 255 (1) cursor was positioned on a user record: this function restores the position 256 to the last record LESS OR EQUAL to the stored record; 257 (2) cursor was positioned on a page infimum record: restores the position to 258 the last record LESS than the user record which was the successor of the page 259 infimum; 260 (3) cursor was positioned on the page supremum: restores to the first record 261 GREATER than the user record which was the predecessor of the supremum. 262 (4) cursor was positioned before the first or after the last in an empty tree: 263 restores to before first or after the last in the tree. 264 @return TRUE if the cursor position was stored when it was on a user 265 record and it can be restored on a user record whose ordering fields 266 are identical to the ones of the original user record */ 267 UNIV_INTERN 268 ibool 269 btr_pcur_restore_position_func( 270 /*===========================*/ 271 ulint latch_mode, /*!< in: BTR_SEARCH_LEAF, ... */ 272 btr_pcur_t* cursor, /*!< in: detached persistent cursor */ 273 const char* file, /*!< in: file name */ 274 ulint line, /*!< in: line where called */ 275 mtr_t* mtr); /*!< in: mtr */ 276 #define btr_pcur_restore_position(l,cur,mtr) \ 277 btr_pcur_restore_position_func(l,cur,__FILE__,__LINE__,mtr) 278 /*********************************************************//** 279 Gets the rel_pos field for a cursor whose position has been stored. 280 @return BTR_PCUR_ON, ... */ 281 UNIV_INLINE 282 ulint 283 btr_pcur_get_rel_pos( 284 /*=================*/ 285 const btr_pcur_t* cursor);/*!< in: persistent cursor */ 286 /**************************************************************//** 287 Commits the mtr and sets the pcur latch mode to BTR_NO_LATCHES, 288 that is, the cursor becomes detached. 289 Function btr_pcur_store_position should be used before calling this, 290 if restoration of cursor is wanted later. */ 291 UNIV_INLINE 292 void 293 btr_pcur_commit_specify_mtr( 294 /*========================*/ 295 btr_pcur_t* pcur, /*!< in: persistent cursor */ 296 mtr_t* mtr); /*!< in: mtr to commit */ 297 /*********************************************************//** 298 Moves the persistent cursor to the next record in the tree. If no records are 299 left, the cursor stays 'after last in tree'. 300 @return TRUE if the cursor was not after last in tree */ 301 UNIV_INLINE 302 ibool 303 btr_pcur_move_to_next( 304 /*==================*/ 305 btr_pcur_t* cursor, /*!< in: persistent cursor; NOTE that the 306 function may release the page latch */ 307 mtr_t* mtr); /*!< in: mtr */ 308 /*********************************************************//** 309 Moves the persistent cursor to the previous record in the tree. If no records 310 are left, the cursor stays 'before first in tree'. 311 @return TRUE if the cursor was not before first in tree */ 312 UNIV_INTERN 313 ibool 314 btr_pcur_move_to_prev( 315 /*==================*/ 316 btr_pcur_t* cursor, /*!< in: persistent cursor; NOTE that the 317 function may release the page latch */ 318 mtr_t* mtr); /*!< in: mtr */ 319 /*********************************************************//** 320 Moves the persistent cursor to the last record on the same page. */ 321 UNIV_INLINE 322 void 323 btr_pcur_move_to_last_on_page( 324 /*==========================*/ 325 btr_pcur_t* cursor, /*!< in: persistent cursor */ 326 mtr_t* mtr); /*!< in: mtr */ 327 /*********************************************************//** 328 Moves the persistent cursor to the next user record in the tree. If no user 329 records are left, the cursor ends up 'after last in tree'. 330 @return TRUE if the cursor moved forward, ending on a user record */ 331 UNIV_INLINE 332 ibool 333 btr_pcur_move_to_next_user_rec( 334 /*===========================*/ 335 btr_pcur_t* cursor, /*!< in: persistent cursor; NOTE that the 336 function may release the page latch */ 337 mtr_t* mtr); /*!< in: mtr */ 338 /*********************************************************//** 339 Moves the persistent cursor to the first record on the next page. 340 Releases the latch on the current page, and bufferunfixes it. 341 Note that there must not be modifications on the current page, 342 as then the x-latch can be released only in mtr_commit. */ 343 UNIV_INTERN 344 void 345 btr_pcur_move_to_next_page( 346 /*=======================*/ 347 btr_pcur_t* cursor, /*!< in: persistent cursor; must be on the 348 last record of the current page */ 349 mtr_t* mtr); /*!< in: mtr */ 350 /*********************************************************//** 351 Moves the persistent cursor backward if it is on the first record 352 of the page. Releases the latch on the current page, and bufferunfixes 353 it. Note that to prevent a possible deadlock, the operation first 354 stores the position of the cursor, releases the leaf latch, acquires 355 necessary latches and restores the cursor position again before returning. 356 The alphabetical position of the cursor is guaranteed to be sensible 357 on return, but it may happen that the cursor is not positioned on the 358 last record of any page, because the structure of the tree may have 359 changed while the cursor had no latches. */ 360 UNIV_INTERN 361 void 362 btr_pcur_move_backward_from_page( 363 /*=============================*/ 364 btr_pcur_t* cursor, /*!< in: persistent cursor, must be on the 365 first record of the current page */ 366 mtr_t* mtr); /*!< in: mtr */ 367 #ifdef UNIV_DEBUG 368 /*********************************************************//** 369 Returns the btr cursor component of a persistent cursor. 370 @return pointer to btr cursor component */ 371 UNIV_INLINE 372 btr_cur_t* 373 btr_pcur_get_btr_cur( 374 /*=================*/ 375 const btr_pcur_t* cursor); /*!< in: persistent cursor */ 376 /*********************************************************//** 377 Returns the page cursor component of a persistent cursor. 378 @return pointer to page cursor component */ 379 UNIV_INLINE 380 page_cur_t* 381 btr_pcur_get_page_cur( 382 /*==================*/ 383 const btr_pcur_t* cursor); /*!< in: persistent cursor */ 384 /*********************************************************//** 385 Returns the page of a persistent cursor. 386 @return pointer to the page */ 387 UNIV_INLINE 388 page_t* 389 btr_pcur_get_page( 390 /*==============*/ 391 const btr_pcur_t* cursor);/*!< in: persistent cursor */ 392 /*********************************************************//** 393 Returns the buffer block of a persistent cursor. 394 @return pointer to the block */ 395 UNIV_INLINE 396 buf_block_t* 397 btr_pcur_get_block( 398 /*===============*/ 399 const btr_pcur_t* cursor);/*!< in: persistent cursor */ 400 /*********************************************************//** 401 Returns the record of a persistent cursor. 402 @return pointer to the record */ 403 UNIV_INLINE 404 rec_t* 405 btr_pcur_get_rec( 406 /*=============*/ 407 const btr_pcur_t* cursor);/*!< in: persistent cursor */ 408 #else /* UNIV_DEBUG */ 409 # define btr_pcur_get_btr_cur(cursor) (&(cursor)->btr_cur) 410 # define btr_pcur_get_page_cur(cursor) (&(cursor)->btr_cur.page_cur) 411 # define btr_pcur_get_page(cursor) ((cursor)->btr_cur.page_cur.block->frame) 412 # define btr_pcur_get_block(cursor) ((cursor)->btr_cur.page_cur.block) 413 # define btr_pcur_get_rec(cursor) ((cursor)->btr_cur.page_cur.rec) 414 #endif /* UNIV_DEBUG */ 415 /*********************************************************//** 416 Checks if the persistent cursor is on a user record. */ 417 UNIV_INLINE 418 ibool 419 btr_pcur_is_on_user_rec( 420 /*====================*/ 421 const btr_pcur_t* cursor);/*!< in: persistent cursor */ 422 /*********************************************************//** 423 Checks if the persistent cursor is after the last user record on 424 a page. */ 425 UNIV_INLINE 426 ibool 427 btr_pcur_is_after_last_on_page( 428 /*===========================*/ 429 const btr_pcur_t* cursor);/*!< in: persistent cursor */ 430 /*********************************************************//** 431 Checks if the persistent cursor is before the first user record on 432 a page. */ 433 UNIV_INLINE 434 ibool 435 btr_pcur_is_before_first_on_page( 436 /*=============================*/ 437 const btr_pcur_t* cursor);/*!< in: persistent cursor */ 438 /*********************************************************//** 439 Checks if the persistent cursor is before the first user record in 440 the index tree. */ 441 UNIV_INLINE 442 ibool 443 btr_pcur_is_before_first_in_tree( 444 /*=============================*/ 445 btr_pcur_t* cursor, /*!< in: persistent cursor */ 446 mtr_t* mtr); /*!< in: mtr */ 447 /*********************************************************//** 448 Checks if the persistent cursor is after the last user record in 449 the index tree. */ 450 UNIV_INLINE 451 ibool 452 btr_pcur_is_after_last_in_tree( 453 /*===========================*/ 454 btr_pcur_t* cursor, /*!< in: persistent cursor */ 455 mtr_t* mtr); /*!< in: mtr */ 456 /*********************************************************//** 457 Moves the persistent cursor to the next record on the same page. */ 458 UNIV_INLINE 459 void 460 btr_pcur_move_to_next_on_page( 461 /*==========================*/ 462 btr_pcur_t* cursor);/*!< in/out: persistent cursor */ 463 /*********************************************************//** 464 Moves the persistent cursor to the previous record on the same page. */ 465 UNIV_INLINE 466 void 467 btr_pcur_move_to_prev_on_page( 468 /*==========================*/ 469 btr_pcur_t* cursor);/*!< in/out: persistent cursor */ 470 /*********************************************************//** 471 Moves the persistent cursor to the infimum record on the same page. */ 472 UNIV_INLINE 473 void 474 btr_pcur_move_before_first_on_page( 475 /*===============================*/ 476 btr_pcur_t* cursor); /*!< in/out: persistent cursor */ 477 478 /** Position state of persistent B-tree cursor. */ 479 enum pcur_pos_t { 480 /** The persistent cursor is not positioned. */ 481 BTR_PCUR_NOT_POSITIONED = 0, 482 /** The persistent cursor was previously positioned. 483 TODO: currently, the state can be BTR_PCUR_IS_POSITIONED, 484 though it really should be BTR_PCUR_WAS_POSITIONED, 485 because we have no obligation to commit the cursor with 486 mtr; similarly latch_mode may be out of date. This can 487 lead to problems if btr_pcur is not used the right way; 488 all current code should be ok. */ 489 BTR_PCUR_WAS_POSITIONED, 490 /** The persistent cursor is positioned by optimistic get to the same 491 record as it was positioned at. Not used for rel_pos == BTR_PCUR_ON. 492 It may need adjustment depending on previous/current search direction 493 and rel_pos. */ 494 BTR_PCUR_IS_POSITIONED_OPTIMISTIC, 495 /** The persistent cursor is positioned by index search. 496 Or optimistic get for rel_pos == BTR_PCUR_ON. */ 497 BTR_PCUR_IS_POSITIONED 498 }; 499 500 /* The persistent B-tree cursor structure. This is used mainly for SQL 501 selects, updates, and deletes. */ 502 503 struct btr_pcur_t{ 504 btr_cur_t btr_cur; /*!< a B-tree cursor */ 505 ulint latch_mode; /*!< see TODO note below! 506 BTR_SEARCH_LEAF, BTR_MODIFY_LEAF, 507 BTR_MODIFY_TREE, or BTR_NO_LATCHES, 508 depending on the latching state of 509 the page and tree where the cursor is 510 positioned; BTR_NO_LATCHES means that 511 the cursor is not currently positioned: 512 we say then that the cursor is 513 detached; it can be restored to 514 attached if the old position was 515 stored in old_rec */ 516 ulint old_stored; /*!< BTR_PCUR_OLD_STORED 517 or BTR_PCUR_OLD_NOT_STORED */ 518 rec_t* old_rec; /*!< if cursor position is stored, 519 contains an initial segment of the 520 latest record cursor was positioned 521 either on, before, or after */ 522 ulint old_n_fields; /*!< number of fields in old_rec */ 523 ulint rel_pos; /*!< BTR_PCUR_ON, BTR_PCUR_BEFORE, or 524 BTR_PCUR_AFTER, depending on whether 525 cursor was on, before, or after the 526 old_rec record */ 527 buf_block_t* block_when_stored;/* buffer block when the position was 528 stored */ 529 ib_uint64_t modify_clock; /*!< the modify clock value of the 530 buffer block when the cursor position 531 was stored */ 532 enum pcur_pos_t pos_state; /*!< btr_pcur_store_position() and 533 btr_pcur_restore_position() state. */ 534 ulint search_mode; /*!< PAGE_CUR_G, ... */ 535 trx_t* trx_if_known; /*!< the transaction, if we know it; 536 otherwise this field is not defined; 537 can ONLY BE USED in error prints in 538 fatal assertion failures! */ 539 /*-----------------------------*/ 540 /* NOTE that the following fields may possess dynamically allocated 541 memory which should be freed if not needed anymore! */ 542 543 byte* old_rec_buf; /*!< NULL, or a dynamically allocated 544 buffer for old_rec */ 545 ulint buf_size; /*!< old_rec_buf size if old_rec_buf 546 is not NULL */ 547 }; 548 549 #define BTR_PCUR_OLD_STORED 908467085 550 #define BTR_PCUR_OLD_NOT_STORED 122766467 551 552 #ifndef UNIV_NONINL 553 #include "btr0pcur.ic" 554 #endif 555 556 #endif 557