summaryrefslogtreecommitdiff
path: root/sql/opt_qpf.h
blob: b82f004ff7f0449fe5cc8df3ffa378a7ef3e3c4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/**************************************************************************************
 
  Query Plan Footprint (QPF) structures

  These structures
  - Can be produced in-expensively from query plan.
  - Store sufficient information to produce either a tabular or a json EXPLAIN
    output
  - Have methods that produce a tabular output.
 
*************************************************************************************/

class QPF_query;

/* 
  A node can be either a SELECT, or a UNION.
*/
class QPF_node : public Sql_alloc
{
public:
  enum qpf_node_type {QPF_UNION, QPF_SELECT, QPF_UPDATE, QPF_DELETE };
  virtual enum qpf_node_type get_type()= 0;


  virtual int get_select_id()= 0;

  /* 
    A node may have children nodes. When a node's QPF (Query Plan Footprint) is
    created, children nodes may not yet have QPFs.  This is why we store ids.
  */
  Dynamic_array<int> children;
  void add_child(int select_no)
  {
    children.append(select_no);
  }

  virtual int print_explain(QPF_query *query, select_result_sink *output, 
                            uint8 explain_flags)=0;
  
  int print_explain_for_children(QPF_query *query, select_result_sink *output, 
                                 uint8 explain_flags);
  virtual ~QPF_node(){}
};


class QPF_table_access;


/*
  Query Plan Footprint of a SELECT.
  
  A select can be:
  1. A degenerate case. In this case, message!=NULL, and it contains a 
     description of what kind of degenerate case it is (e.g. "Impossible 
     WHERE").
  2. a non-degenrate join. In this case, join_tabs describes the join.

  In the non-degenerate case, a SELECT may have a GROUP BY/ORDER BY operation.

  In both cases, the select may have children nodes. class QPF_node provides
  a way get node's children.
*/

class QPF_select : public QPF_node
{
public:
  enum qpf_node_type get_type() { return QPF_SELECT; }

  QPF_select() : 
    message(NULL), join_tabs(NULL),
    using_temporary(false), using_filesort(false)
  {}
  
  ~QPF_select();

  bool add_table(QPF_table_access *tab)
  {
    if (!join_tabs)
    {
      join_tabs= (QPF_table_access**) my_malloc(sizeof(QPF_table_access*) *
                                                MAX_TABLES, MYF(0));
      n_join_tabs= 0;
    }
    join_tabs[n_join_tabs++]= tab;
    return false;
  }

public:
  int select_id;
  const char *select_type;

  int get_select_id() { return select_id; }

  /*
    If message != NULL, this is a degenerate join plan, and all subsequent
    members have no info 
  */
  const char *message;
  
  /*
    A flat array of Query Plan Footprints. The order is "just like EXPLAIN
    would print them".
  */
  QPF_table_access** join_tabs;
  uint n_join_tabs;

  /* Global join attributes. In tabular form, they are printed on the first row */
  bool using_temporary;
  bool using_filesort;
  
  int print_explain(QPF_query *query, select_result_sink *output, 
                    uint8 explain_flags);
};


/* 
  Query Plan Footprint of a UNION.

  A UNION may or may not have "Using filesort".
*/

class QPF_union : public QPF_node
{
public:
  enum qpf_node_type get_type() { return QPF_UNION; }

  int get_select_id()
  {
    DBUG_ASSERT(union_members.elements() > 0);
    return union_members.at(0);
  }
  /*
    Members of the UNION.  Note: these are different from UNION's "children".
    Example:

      (select * from t1) union 
      (select * from t2) order by (select col1 from t3 ...)

    here 
      - select-from-t1 and select-from-t2 are "union members",
      - select-from-t3 is the only "child".
  */
  Dynamic_array<int> union_members;

  void add_select(int select_no)
  {
    union_members.append(select_no);
  }
  void push_table_name(List<Item> *item_list);
  int print_explain(QPF_query *query, select_result_sink *output, 
                    uint8 explain_flags);

  const char *fake_select_type;
  bool using_filesort;
};

class QPF_delete;


/*
  Query Plan Footprint (QPF) for a query (i.e. a statement).

  This should be able to survive when the query plan was deleted. Currently, 
  we do not intend for it survive until after query's MEM_ROOT is freed. It
  does surivive freeing of query's items.
   
  For reference, the process of post-query cleanup is as follows:

    >dispatch_command
    | >mysql_parse
    | |  ...
    | | lex_end()
    | |  ...
    | | >THD::cleanup_after_query
    | | | ...
    | | | free_items()
    | | | ...
    | | <THD::cleanup_after_query
    | |
    | <mysql_parse
    |
    | log_slow_statement()
    | 
    | free_root()
    | 
    >dispatch_command
  
  That is, the order of actions is:
    - free query's Items
    - write to slow query log 
    - free query's MEM_ROOT
    
*/

class QPF_query : public Sql_alloc
{
public:
  QPF_query();
  ~QPF_query();
  /* Add a new node */
  void add_node(QPF_node *node);

  /* This will return a select, or a union */
  QPF_node *get_node(uint select_id);

  /* This will return a select (even if there is a union with this id) */
  QPF_select *get_select(uint select_id);
  
  QPF_union *get_union(uint select_id);
  
  /* QPF_delete inherits from QPF_update */
  QPF_update *upd_del_plan;

  /* Produce a tabular EXPLAIN output */
  int print_explain(select_result_sink *output, uint8 explain_flags);
  
  /* If true, at least part of EXPLAIN can be printed */
  bool have_query_plan() { return upd_del_plan!= NULL || get_node(1) != NULL; }
  MEM_ROOT *mem_root;
private:
  Dynamic_array<QPF_union*> unions;
  Dynamic_array<QPF_select*> selects;
  
  /* 
    Debugging aid: count how many times add_node() was called. Ideally, it
    should be one, we currently allow O(1) query plan saves for each
    select or union.  The goal is not to have O(#rows_in_some_table), which 
    is unacceptable.
  */
  longlong operations;
};


enum Extra_tag
{
  ET_none= 0, /* not-a-tag */
  ET_USING_INDEX_CONDITION,
  ET_USING_INDEX_CONDITION_BKA,
  ET_USING, /* For quick selects of various kinds */
  ET_RANGE_CHECKED_FOR_EACH_RECORD,
  ET_USING_WHERE_WITH_PUSHED_CONDITION,
  ET_USING_WHERE,
  ET_NOT_EXISTS,

  ET_USING_INDEX,
  ET_FULL_SCAN_ON_NULL_KEY,
  ET_SKIP_OPEN_TABLE,
  ET_OPEN_FRM_ONLY,
  ET_OPEN_FULL_TABLE,

  ET_SCANNED_0_DATABASES,
  ET_SCANNED_1_DATABASE,
  ET_SCANNED_ALL_DATABASES,

  ET_USING_INDEX_FOR_GROUP_BY,

  ET_USING_MRR, // does not print "Using mrr". 

  ET_DISTINCT,
  ET_LOOSESCAN,
  ET_START_TEMPORARY,
  ET_END_TEMPORARY,
  ET_FIRST_MATCH,
  
  ET_USING_JOIN_BUFFER,

  ET_CONST_ROW_NOT_FOUND,
  ET_UNIQUE_ROW_NOT_FOUND,
  ET_IMPOSSIBLE_ON_CONDITION,

  ET_total
};


/*
  Query Plan Footprint for a JOIN_TAB.
*/
class QPF_table_access : public Sql_alloc
{
public:
  void push_extra(enum Extra_tag extra_tag);

  /* Internals */
public:
  /* 
    0 means this tab is not inside SJM nest and should use QPF_select's id
    other value means the tab is inside an SJM nest.
  */
  int sjm_nest_select_id;

  /* id and 'select_type' are cared-of by the parent QPF_select */
  TABLE *table;
  StringBuffer<64> table_name;

  enum join_type type;

  StringBuffer<64> used_partitions;
  bool used_partitions_set;

  key_map possible_keys;
  StringBuffer<64> possible_keys_str;
  
  /* Not used? */
  uint key_no;
  uint key_length;

  bool key_set; /* not set means 'NULL' should be printed */
  StringBuffer<64> key;

  bool key_len_set; /* not set means 'NULL' should be printed */
  StringBuffer<64> key_len;

  bool ref_set; /* not set means 'NULL' should be printed */
  StringBuffer<64> ref;

  bool rows_set; /* not set means 'NULL' should be printed */
  ha_rows rows;

  bool filtered_set; /* not set means 'NULL' should be printed */
  double filtered;

  /* 
    Contents of the 'Extra' column. Some are converted into strings, some have
    parameters, values for which are stored below.
  */
  Dynamic_array<enum Extra_tag> extra_tags;

  // Valid if ET_USING tag is present
  StringBuffer<64> quick_info;

  // Valid if ET_USING_INDEX_FOR_GROUP_BY is present
  StringBuffer<64> loose_scan_type;
  
  // valid with ET_RANGE_CHECKED_FOR_EACH_RECORD
  key_map range_checked_map;

  // valid with ET_USING_MRR
  StringBuffer<64> mrr_type;

  // valid with ET_USING_JOIN_BUFFER
  StringBuffer<64> join_buffer_type;
  
  //TABLE *firstmatch_table;
  StringBuffer<64> firstmatch_table_name;

  int print_explain(select_result_sink *output, uint8 explain_flags, 
                    uint select_id, const char *select_type,
                    bool using_temporary, bool using_filesort);
private:
  void append_tag_name(String *str, enum Extra_tag tag);
};


/*
  Query Plan Footprint for single-table UPDATE. 
  
  This is similar to QPF_table_access, except that it is more restrictive.
  Also, it can have UPDATE operation options, but currently there aren't any.
*/

class QPF_update : public QPF_node
{
public:
  virtual enum qpf_node_type get_type() { return QPF_UPDATE; }
  virtual int get_select_id() { return 1; /* always root */ }

  const char *select_type;

  bool impossible_where;
  StringBuffer<64> table_name;

  enum join_type jtype;
  StringBuffer<128> possible_keys_line;
  StringBuffer<128> key_str;
  StringBuffer<128> key_len_str;
  StringBuffer<64> mrr_type;
  
  bool using_where;
  ha_rows rows;

  bool using_filesort;

  virtual int print_explain(QPF_query *query, select_result_sink *output, 
                            uint8 explain_flags);
};


/* 
  Query Plan Footprint for a single-table DELETE.
*/

class QPF_delete: public QPF_update
{
public:
  /*
    TRUE means we're going to call handler->delete_all_rows() and not read any
    rows.
  */
  bool deleting_all_rows;

  virtual enum qpf_node_type get_type() { return QPF_DELETE; }
  virtual int get_select_id() { return 1; /* always root */ }

  virtual int print_explain(QPF_query *query, select_result_sink *output, 
                            uint8 explain_flags);
};