1 #ifndef SQL_EXECUTOR_INCLUDED 2 #define SQL_EXECUTOR_INCLUDED 3 4 /* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights 5 * reserved. 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License, version 2.0, 9 as published by the Free Software Foundation. 10 11 This program is also distributed with certain software (including 12 but not limited to OpenSSL) that is licensed under separate terms, 13 as designated in a particular file or component or in included license 14 documentation. The authors of MySQL hereby grant you an additional 15 permission to link the program and your derivative works with the 16 separately licensed software that they have included with MySQL. 17 18 This program is distributed in the hope that it will be useful, 19 but WITHOUT ANY WARRANTY; without even the implied warranty of 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 GNU General Public License, version 2.0, for more details. 22 23 You should have received a copy of the GNU General Public License 24 along with this program; if not, write to the Free Software 25 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ 26 27 /** @file Classes for query execution */ 28 29 #include "records.h" // READ_RECORD 30 #include "sql_opt_exec_shared.h" // QEP_shared_owner 31 32 class JOIN; 33 class JOIN_TAB; 34 class QEP_TAB; 35 typedef struct st_table_ref TABLE_REF; 36 typedef struct st_position POSITION; 37 38 /** 39 Possible status of a "nested loop" operation (Next_select_func family of 40 functions). 41 All values except NESTED_LOOP_OK abort the nested loop. 42 */ 43 enum enum_nested_loop_state 44 { 45 /** 46 Thread shutdown was requested while processing the record 47 @todo could it be merged with NESTED_LOOP_ERROR? Why two distinct states? 48 */ 49 NESTED_LOOP_KILLED= -2, 50 /// A fatal error (like table corruption) was detected 51 NESTED_LOOP_ERROR= -1, 52 /// Record has been successfully handled 53 NESTED_LOOP_OK= 0, 54 /** 55 Record has been successfully handled; additionally, the nested loop 56 produced the number of rows specified in the LIMIT clause for the query. 57 */ 58 NESTED_LOOP_QUERY_LIMIT= 3, 59 /** 60 Record has been successfully handled; additionally, there is a cursor and 61 the nested loop algorithm produced the number of rows that is specified 62 for current cursor fetch operation. 63 */ 64 NESTED_LOOP_CURSOR_LIMIT= 4 65 }; 66 67 68 typedef enum_nested_loop_state 69 (*Next_select_func)(JOIN *, class QEP_TAB *, bool); 70 71 /* 72 Temporary table used by semi-join DuplicateElimination strategy 73 74 This consists of the temptable itself and data needed to put records 75 into it. The table's DDL is as follows: 76 77 CREATE TABLE tmptable (col VARCHAR(n) BINARY, PRIMARY KEY(col)); 78 79 where the primary key can be replaced with unique constraint if n exceeds 80 the limit (as it is always done for query execution-time temptables). 81 82 The record value is a concatenation of rowids of tables from the join we're 83 executing. If a join table is on the inner side of the outer join, we 84 assume that its rowid can be NULL and provide means to store this rowid in 85 the tuple. 86 */ 87 88 class SJ_TMP_TABLE : public Sql_alloc 89 { 90 public: SJ_TMP_TABLE()91 SJ_TMP_TABLE():hash_field(NULL) 92 {} 93 /* 94 Array of pointers to tables whose rowids compose the temporary table 95 record. 96 */ 97 class TAB 98 { 99 public: 100 QEP_TAB *qep_tab; 101 uint rowid_offset; 102 ushort null_byte; 103 uchar null_bit; 104 }; 105 TAB *tabs; 106 TAB *tabs_end; 107 108 /* 109 is_confluent==TRUE means this is a special case where the temptable record 110 has zero length (and presence of a unique key means that the temptable can 111 have either 0 or 1 records). 112 In this case we don't create the physical temptable but instead record 113 its state in SJ_TMP_TABLE::have_confluent_record. 114 */ 115 bool is_confluent; 116 117 /* 118 When is_confluent==TRUE: the contents of the table (whether it has the 119 record or not). 120 */ 121 bool have_confluent_row; 122 123 /* table record parameters */ 124 uint null_bits; 125 uint null_bytes; 126 uint rowid_len; 127 128 /* The temporary table itself (NULL means not created yet) */ 129 TABLE *tmp_table; 130 131 /* 132 These are the members we got from temptable creation code. We'll need 133 them if we'll need to convert table from HEAP to MyISAM/Maria. 134 */ 135 MI_COLUMNDEF *start_recinfo; 136 MI_COLUMNDEF *recinfo; 137 138 /* Pointer to next table (next->start_idx > this->end_idx) */ 139 SJ_TMP_TABLE *next; 140 /* Calc hash instead of too long key */ 141 Field_longlong *hash_field; 142 }; 143 144 145 /** 146 Executor structure for the materialized semi-join info, which contains 147 - Description of expressions selected from subquery 148 - The sj-materialization temporary table 149 */ 150 class Semijoin_mat_exec : public Sql_alloc 151 { 152 public: Semijoin_mat_exec(TABLE_LIST * sj_nest,bool is_scan,uint table_count,uint mat_table_index,uint inner_table_index)153 Semijoin_mat_exec(TABLE_LIST *sj_nest, bool is_scan, uint table_count, 154 uint mat_table_index, uint inner_table_index) 155 :sj_nest(sj_nest), is_scan(is_scan), table_count(table_count), 156 mat_table_index(mat_table_index), inner_table_index(inner_table_index), 157 table_param(), table(NULL) 158 {} ~Semijoin_mat_exec()159 ~Semijoin_mat_exec() 160 {} 161 TABLE_LIST *const sj_nest; ///< Semi-join nest for this materialization 162 const bool is_scan; ///< TRUE if executing a scan, FALSE if lookup 163 const uint table_count; ///< Number of tables in the sj-nest 164 const uint mat_table_index; ///< Index in join_tab for materialized table 165 const uint inner_table_index; ///< Index in join_tab for first inner table 166 Temp_table_param table_param; ///< The temptable and its related info 167 TABLE *table; ///< Reference to temporary table 168 }; 169 170 171 172 /** 173 QEP_operation is an interface class for operations in query execution plan. 174 175 Currently following operations are implemented: 176 JOIN_CACHE - caches partial join result and joins with attached table 177 QEP_tmp_table - materializes join result in attached table 178 179 An operation's life cycle is as follows: 180 .) it is initialized on the init() call 181 .) accumulates records one by one when put_record() is called. 182 .) finalize record sending when end_send() is called. 183 .) free all internal buffers on the free() call. 184 185 Each operation is attached to a join_tab, to which exactly depends on the 186 operation type: JOIN_CACHE is attached to the table following the table 187 being cached, QEP_tmp_buffer is attached to a tmp table. 188 */ 189 190 class QEP_operation :public Sql_alloc 191 { 192 public: 193 // Type of the operation 194 enum enum_op_type { OT_CACHE, OT_TMP_TABLE }; 195 /** 196 For JOIN_CACHE : Table to be joined with the partial join records from 197 the cache 198 For JOIN_TMP_BUFFER : join_tab of tmp table 199 */ 200 QEP_TAB *qep_tab; 201 QEP_operation()202 QEP_operation(): qep_tab(NULL) {}; QEP_operation(QEP_TAB * qep_tab_arg)203 QEP_operation(QEP_TAB *qep_tab_arg): qep_tab(qep_tab_arg) {}; ~QEP_operation()204 virtual ~QEP_operation() {}; 205 virtual enum_op_type type()= 0; 206 /** 207 Initialize operation's internal state. Called once per query execution. 208 */ init()209 virtual int init() { return 0; }; 210 /** 211 Put a new record into the operation's buffer 212 @return 213 return one of enum_nested_loop_state values. 214 */ 215 virtual enum_nested_loop_state put_record()= 0; 216 /** 217 Finalize records sending. 218 */ 219 virtual enum_nested_loop_state end_send()= 0; 220 /** 221 Internal state cleanup. 222 */ mem_free()223 virtual void mem_free() {}; 224 }; 225 226 227 /** 228 @brief 229 Class for accumulating join result in a tmp table, grouping them if 230 necessary, and sending further. 231 232 @details 233 Join result records are accumulated on the put_record() call. 234 The accumulation process is determined by the write_func, it could be: 235 end_write Simply store all records in tmp table. 236 end_write_group Perform grouping using join->group_fields, 237 records are expected to be sorted. 238 end_update Perform grouping using the key generated on tmp 239 table. Input records aren't expected to be sorted. 240 Tmp table uses the heap engine 241 end_update_unique Same as above, but the engine is myisam. 242 243 Lazy table initialization is used - the table will be instantiated and 244 rnd/index scan started on the first put_record() call. 245 246 */ 247 248 class QEP_tmp_table :public QEP_operation 249 { 250 public: QEP_tmp_table(QEP_TAB * qep_tab_arg)251 QEP_tmp_table(QEP_TAB *qep_tab_arg) : 252 QEP_operation(qep_tab_arg), write_func(NULL) 253 {}; type()254 enum_op_type type() { return OT_TMP_TABLE; } put_record()255 enum_nested_loop_state put_record() { return put_record(false); }; 256 /* 257 Send the result of operation further (to a next operation/client) 258 This function is called after all records were put into the buffer 259 (determined by the caller). 260 261 @return return one of enum_nested_loop_state values. 262 */ 263 enum_nested_loop_state end_send(); 264 /** write_func setter */ set_write_func(Next_select_func new_write_func)265 void set_write_func(Next_select_func new_write_func) 266 { 267 write_func= new_write_func; 268 } 269 270 private: 271 /** Write function that would be used for saving records in tmp table. */ 272 Next_select_func write_func; 273 enum_nested_loop_state put_record(bool end_of_records); 274 MY_ATTRIBUTE((warn_unused_result)) 275 bool prepare_tmp_table(); 276 }; 277 278 279 void setup_tmptable_write_func(QEP_TAB *tab); 280 enum_nested_loop_state sub_select_op(JOIN *join, QEP_TAB *qep_tab, bool 281 end_of_records); 282 enum_nested_loop_state end_send_group(JOIN *join, QEP_TAB *qep_tab, 283 bool end_of_records); 284 enum_nested_loop_state end_write_group(JOIN *join, QEP_TAB *qep_tab, 285 bool end_of_records); 286 enum_nested_loop_state sub_select(JOIN *join,QEP_TAB *qep_tab, bool 287 end_of_records); 288 enum_nested_loop_state 289 evaluate_join_record(JOIN *join, QEP_TAB *qep_tab, int error); 290 291 292 293 MY_ATTRIBUTE((warn_unused_result)) 294 bool copy_fields(Temp_table_param *param, const THD *thd); 295 296 bool copy_funcs(Func_ptr_array*, const THD *thd); 297 bool cp_buffer_from_ref(THD *thd, TABLE *table, TABLE_REF *ref); 298 299 /** Help function when we get some an error from the table handler. */ 300 int report_handler_error(TABLE *table, int error); 301 302 int safe_index_read(QEP_TAB *tab); 303 st_sort_field * make_unireg_sortorder(ORDER *order, uint *length, 304 st_sort_field *sortorder); 305 306 int join_read_const_table(JOIN_TAB *tab, POSITION *pos); 307 void join_read_key_unlock_row(st_join_table *tab); 308 void join_const_unlock_row(st_join_table *tab); 309 int join_init_quick_read_record(QEP_TAB *tab); 310 int join_init_read_record(QEP_TAB *tab); 311 int join_read_first(QEP_TAB *tab); 312 int join_read_last(QEP_TAB *tab); 313 int join_read_last_key(QEP_TAB *tab); 314 int join_materialize_derived(QEP_TAB *tab); 315 int join_materialize_semijoin(QEP_TAB *tab); 316 int join_read_prev_same(READ_RECORD *info); 317 318 int do_sj_dups_weedout(THD *thd, SJ_TMP_TABLE *sjtbl); 319 int test_if_item_cache_changed(List<Cached_item> &list); 320 321 // Create list for using with tempory table 322 bool change_to_use_tmp_fields(THD *thd, Ref_ptr_array ref_pointer_array, 323 List<Item> &new_list1, 324 List<Item> &new_list2, 325 uint elements, List<Item> &items); 326 // Create list for using with tempory table 327 bool change_refs_to_tmp_fields(THD *thd, Ref_ptr_array ref_pointer_array, 328 List<Item> &new_list1, 329 List<Item> &new_list2, 330 uint elements, List<Item> &items); 331 bool alloc_group_fields(JOIN *join, ORDER *group); 332 bool prepare_sum_aggregators(Item_sum **func_ptr, bool need_distinct); 333 bool setup_sum_funcs(THD *thd, Item_sum **func_ptr); 334 bool make_group_fields(JOIN *main_join, JOIN *curr_join); 335 bool setup_copy_fields(THD *thd, Temp_table_param *param, 336 Ref_ptr_array ref_pointer_array, 337 List<Item> &res_selected_fields, List<Item> &res_all_fields, 338 uint elements, List<Item> &all_fields); 339 bool check_unique_constraint(TABLE *table); 340 ulonglong unique_hash(Field *field, ulonglong *hash); 341 342 class Opt_trace_object; 343 344 class QEP_TAB : public Sql_alloc, public QEP_shared_owner 345 { 346 public: QEP_TAB()347 QEP_TAB() : 348 QEP_shared_owner(), 349 table_ref(NULL), 350 flush_weedout_table(NULL), 351 check_weed_out_table(NULL), 352 firstmatch_return(NO_PLAN_IDX), 353 loosescan_key_len(0), 354 loosescan_buf(NULL), 355 match_tab(NO_PLAN_IDX), 356 found_match(false), 357 found(false), 358 not_null_compl(false), 359 first_unmatched(NO_PLAN_IDX), 360 materialized(false), 361 materialize_table(NULL), 362 read_first_record(NULL), 363 next_select(NULL), 364 read_record(), 365 save_read_first_record(NULL), 366 save_read_record(NULL), 367 used_null_fields(false), 368 used_uneven_bit_fields(false), 369 keep_current_rowid(false), 370 copy_current_rowid(NULL), 371 distinct(false), 372 not_used_in_distinct(false), 373 cache_idx_cond(NULL), 374 having(NULL), 375 op(NULL), 376 tmp_table_param(NULL), 377 filesort(NULL), 378 fields(NULL), 379 all_fields(NULL), 380 ref_array(NULL), 381 send_records(0), 382 quick_traced_before(false), 383 m_condition_optim(NULL), 384 m_quick_optim(NULL), 385 m_keyread_optim(false) 386 { 387 /** 388 @todo Add constructor to READ_RECORD. 389 All users do init_read_record(), which does memset(), 390 rather than invoking a constructor. 391 */ 392 } 393 394 /// Initializes the object from a JOIN_TAB 395 void init(JOIN_TAB *jt); 396 // Cleans up. 397 void cleanup(); 398 399 // Getters and setters 400 condition_optim()401 Item *condition_optim() const { return m_condition_optim; } quick_optim()402 QUICK_SELECT_I *quick_optim() const { return m_quick_optim; } set_quick_optim()403 void set_quick_optim() { m_quick_optim= quick(); } set_condition_optim()404 void set_condition_optim() { m_condition_optim= condition(); } keyread_optim()405 bool keyread_optim() const { return m_keyread_optim; } set_keyread_optim()406 void set_keyread_optim() 407 { 408 if (table()) 409 m_keyread_optim= table()->key_read; 410 } 411 set_table(TABLE * t)412 void set_table(TABLE *t) 413 { 414 m_qs->set_table(t); 415 if (t) 416 t->reginfo.qep_tab= this; 417 } 418 419 /// @returns semijoin strategy for this table. 420 uint get_sj_strategy() const; 421 422 /// Return true if join_tab should perform a FirstMatch action do_firstmatch()423 bool do_firstmatch() const { return firstmatch_return != NO_PLAN_IDX; } 424 425 /// Return true if join_tab should perform a LooseScan action do_loosescan()426 bool do_loosescan() const { return loosescan_key_len; } 427 428 /// Return true if join_tab starts a Duplicate Weedout action starts_weedout()429 bool starts_weedout() const { return flush_weedout_table; } 430 431 /// Return true if join_tab finishes a Duplicate Weedout action finishes_weedout()432 bool finishes_weedout() const { return check_weed_out_table; } 433 434 bool prepare_scan(); 435 436 /** 437 A helper function that allocates appropriate join cache object and 438 sets next_select function of previous tab. 439 */ 440 void init_join_cache(JOIN_TAB *join_tab); 441 442 /** 443 @returns query block id for an inner table of materialized semi-join, and 444 0 for all other tables. 445 @note implementation is not efficient (loops over all tables) - use this 446 function only in EXPLAIN. 447 */ 448 uint sjm_query_block_id() const; 449 450 /// @returns whether this is doing QS_DYNAMIC_RANGE dynamic_range()451 bool dynamic_range() const 452 { 453 if (!position()) 454 return false; // tmp table 455 return read_first_record == join_init_quick_read_record; 456 } 457 458 bool use_order() const; ///< Use ordering provided by chosen index? 459 bool sort_table(); 460 bool remove_duplicates(); 461 skip_record(THD * thd,bool * skip_record_arg)462 inline bool skip_record(THD *thd, bool *skip_record_arg) 463 { 464 *skip_record_arg= condition() ? condition()->val_int() == FALSE : FALSE; 465 return thd->is_error(); 466 } 467 468 /** 469 Used to begin a new execution of a subquery. Necessary if this subquery 470 has done a filesort which which has cleared condition/quick. 471 */ restore_quick_optim_and_condition()472 void restore_quick_optim_and_condition() 473 { 474 if (m_condition_optim) 475 set_condition(m_condition_optim); 476 if (m_quick_optim) 477 set_quick(m_quick_optim); 478 } 479 480 void pick_table_access_method(const JOIN_TAB *join_tab); 481 void set_pushed_table_access_method(void); 482 void push_index_cond(const JOIN_TAB *join_tab, 483 uint keyno, Opt_trace_object *trace_obj); 484 485 /// @return the index used for a table in a QEP 486 uint effective_index() const; 487 488 bool pfs_batch_update(JOIN *join); 489 490 public: 491 /// Pointer to table reference 492 TABLE_LIST *table_ref; 493 494 /* Variables for semi-join duplicate elimination */ 495 SJ_TMP_TABLE *flush_weedout_table; 496 SJ_TMP_TABLE *check_weed_out_table; 497 498 /* 499 If set, means we should stop join enumeration after we've got the first 500 match and return to the specified join tab. May be PRE_FIRST_PLAN_IDX 501 which means stopping join execution after the first match. 502 */ 503 plan_idx firstmatch_return; 504 505 /* 506 Length of key tuple (depends on #keyparts used) to store in loosescan_buf. 507 If zero, means that loosescan is not used. 508 */ 509 uint loosescan_key_len; 510 511 /* Buffer to save index tuple to be able to skip duplicates */ 512 uchar *loosescan_buf; 513 514 /* 515 If doing a LooseScan, this QEP is the first (i.e. "driving") 516 QEP_TAB, and match_tab points to the last QEP_TAB handled by the strategy. 517 match_tab->found_match should be checked to see if the current value group 518 had a match. 519 If doing a FirstMatch, check this QEP_TAB to see if there is a match. 520 Unless the FirstMatch performs a "split jump", this is equal to the 521 current QEP_TAB. 522 */ 523 plan_idx match_tab; 524 525 /* 526 Used by FirstMatch and LooseScan. TRUE <=> there is a matching 527 record combination 528 */ 529 bool found_match; 530 531 /** 532 Used to decide whether an inner table of an outer join should produce NULL 533 values. If it is true after a call to evaluate_join_record(), the join 534 condition has been satisfied for at least one row from the inner 535 table. This member is not really manipulated by this class, see sub_select 536 for details on its use. 537 */ 538 bool found; 539 540 /** 541 This member is true as long as we are evaluating rows from the inner 542 tables of an outer join. If none of these rows satisfy the join condition, 543 we generated NULL-complemented rows and set this member to false. In the 544 meantime, the value may be read by triggered conditions, see 545 Item_func_trig_cond::val_int(). 546 */ 547 bool not_null_compl; 548 549 plan_idx first_unmatched; /**< used for optimization purposes only */ 550 551 /// For a materializable derived or SJ table: true if has been materialized 552 bool materialized; 553 554 READ_RECORD::Setup_func materialize_table; 555 /** 556 Initialize table for reading and fetch the first row from the table. If 557 table is a materialized derived one, function must materialize it with 558 prepare_scan(). 559 */ 560 READ_RECORD::Setup_func read_first_record; 561 Next_select_func next_select; 562 READ_RECORD read_record; 563 /* 564 The following two fields are used for a [NOT] IN subquery if it is 565 executed by an alternative full table scan when the left operand of 566 the subquery predicate is evaluated to NULL. 567 */ 568 READ_RECORD::Setup_func save_read_first_record;/* to save read_first_record */ 569 READ_RECORD::Read_func save_read_record;/* to save read_record.read_record */ 570 571 // join-cache-related members 572 bool used_null_fields; 573 bool used_uneven_bit_fields; 574 575 /* 576 Used by DuplicateElimination. tab->table->ref must have the rowid 577 whenever we have a current record. copy_current_rowid needed because 578 we cannot bind to the rowid buffer before the table has been opened. 579 */ 580 bool keep_current_rowid; 581 st_cache_field *copy_current_rowid; 582 583 /** TRUE <=> remove duplicates on this table. */ 584 bool distinct; 585 586 bool not_used_in_distinct; 587 588 /// Index condition for BKA access join 589 Item *cache_idx_cond; 590 591 /** HAVING condition for checking prior saving a record into tmp table*/ 592 Item *having; 593 594 QEP_operation *op; 595 596 /* Tmp table info */ 597 Temp_table_param *tmp_table_param; 598 599 /* Sorting related info */ 600 Filesort *filesort; 601 602 /** 603 List of topmost expressions in the select list. The *next* JOIN TAB 604 in the plan should use it to obtain correct values. Same applicable to 605 all_fields. These lists are needed because after tmp tables functions 606 will be turned to fields. These variables are pointing to 607 tmp_fields_list[123]. Valid only for tmp tables and the last non-tmp 608 table in the query plan. 609 @see JOIN::make_tmp_tables_info() 610 */ 611 List<Item> *fields; 612 /** List of all expressions in the select list */ 613 List<Item> *all_fields; 614 /* 615 Pointer to the ref array slice which to switch to before sending 616 records. Valid only for tmp tables. 617 */ 618 Ref_ptr_array *ref_array; 619 620 /** Number of records saved in tmp table */ 621 ha_rows send_records; 622 623 /** 624 Used for QS_DYNAMIC_RANGE, i.e., "Range checked for each record". 625 Used by optimizer tracing to decide whether or not dynamic range 626 analysis of this select has been traced already. If optimizer 627 trace option DYNAMIC_RANGE is enabled, range analysis will be 628 traced with different ranges for every record to the left of this 629 table in the join. If disabled, range analysis will only be traced 630 for the first range. 631 */ 632 bool quick_traced_before; 633 634 /// @See m_quick_optim 635 Item *m_condition_optim; 636 637 /** 638 m_quick is the quick "to be used at this stage of execution". 639 It can happen that filesort uses the quick (produced by the optimizer) to 640 produce a sorted result, then the read of this result has to be done 641 without "quick", so we must reset m_quick to NULL, but we want to delay 642 freeing of m_quick or it would close the filesort's result and the table 643 prematurely. 644 In that case, we move m_quick to m_quick_optim (=> delay deletion), reset 645 m_quick to NULL (read of filesort's result will be without quick); if 646 this is a subquery which is later executed a second time, 647 QEP_TAB::reset() will restore the quick from m_quick_optim into m_quick. 648 quick_optim stands for "the quick decided by the optimizer". 649 EXPLAIN reads this member and m_condition_optim; so if you change them 650 after exposing the plan (setting plan_state), do it with the 651 LOCK_query_plan mutex. 652 */ 653 QUICK_SELECT_I *m_quick_optim; 654 655 /** 656 True if only index is going to be read for this table. This is the 657 optimizer's decision. 658 */ 659 bool m_keyread_optim; 660 661 QEP_TAB(const QEP_TAB&); // not defined 662 QEP_TAB& operator=(const QEP_TAB&); // not defined 663 }; 664 665 666 /** 667 @returns a pointer to the QEP_TAB whose index is qtab->member. For 668 example, QEP_AT(x,first_inner) is the first_inner table of x. 669 */ 670 #define QEP_AT(qtab,member) (qtab->join()->qep_tab[qtab->member]) 671 672 673 /** 674 Use this class when you need a QEP_TAB not connected to any JOIN_TAB. 675 */ 676 class QEP_TAB_standalone : public Sql_alloc 677 { 678 public: QEP_TAB_standalone()679 QEP_TAB_standalone() { m_qt.set_qs(&m_qs); } ~QEP_TAB_standalone()680 ~QEP_TAB_standalone() { m_qt.cleanup(); } 681 /// @returns access to the QEP_TAB as_QEP_TAB()682 QEP_TAB &as_QEP_TAB() { return m_qt; } 683 private: 684 QEP_shared m_qs; 685 QEP_TAB m_qt; 686 }; 687 688 #endif /* SQL_EXECUTOR_INCLUDED */ 689