summaryrefslogtreecommitdiff
path: root/sql/sql_cte.cc
blob: 77f0bcf04ba17567ce2e59788c721ced734ab0dd (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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
#include "sql_class.h"
#include "sql_lex.h"
#include "sql_cte.h"
#include "sql_view.h"    // for make_valid_column_names
#include "sql_parse.h"


/**
  @brief
    Check dependencies between tables defined in a list of with clauses
 
  @param 
    with_clauses_list  Pointer to the first clause in the list

  @details
    The procedure just calls the method With_clause::check_dependencies
    for each member of the given list.

  @retval
    false   on success
    true    on failure
*/

bool check_dependencies_in_with_clauses(With_clause *with_clauses_list)
{
  for (With_clause *with_clause= with_clauses_list;
       with_clause;
       with_clause= with_clause->next_with_clause)
  {
    if (with_clause->check_dependencies())
      return true;
  }
  return false;
}


/**
  @brief
    Check dependencies between tables defined in this with clause

 @details
    The method performs the following actions for this with clause:

    1. Test for definitions of the tables with the same name.
    2. For each table T defined in this with clause look for tables
       from the same with clause that are used in the query that
       specifies T and set the dependencies of T on these tables
       in dependency_map. 
    3. Build the transitive closure of the above direct dependencies
       to find out all recursive definitions.
    4. If this with clause is not specified as recursive then
       for each with table T defined in this with clause check whether
       it is used in any definition that follows the definition of T.

  @retval
    true    if an error is reported 
    false   otherwise
*/

bool With_clause::check_dependencies()
{
  if (dependencies_are_checked)
    return false;
  /* 
    Look for for definitions with the same query name.
    When found report an error and return true immediately.
    For each table T defined in this with clause look for all other tables from
    the same with with clause that are used in the specification of T.
    For each such table set the dependency bit in the dependency map of
    with element for T.
  */
  for (With_element *with_elem= first_elem;
       with_elem != NULL;
       with_elem= with_elem->next_elem)
  {
    for (With_element *elem= first_elem;
         elem != with_elem;
         elem= elem->next_elem)
    {
      if (my_strcasecmp(system_charset_info, with_elem->query_name->str,
                        elem->query_name->str) == 0)
      {
	my_error(ER_DUP_QUERY_NAME, MYF(0), with_elem->query_name->str);
	return true;
      }
    }
    with_elem->check_dependencies_in_unit(with_elem->spec);   
  }
  /* Build the transitive closure of the direct dependencies found above */
  for (With_element *with_elem= first_elem;
       with_elem != NULL;
       with_elem= with_elem->next_elem)
  {
    table_map with_elem_map=  with_elem->get_elem_map();
    for (With_element *elem= first_elem; elem != NULL; elem= elem->next_elem)
    {
      if (elem->dependency_map & with_elem_map)
        elem->dependency_map |= with_elem->dependency_map;
    }   
  }

  /*
    Mark those elements where tables are defined with direct or indirect recursion.
    Report an error when recursion (direct or indirect) is used to define a table.
  */ 
  for (With_element *with_elem= first_elem;
       with_elem != NULL;
       with_elem= with_elem->next_elem)
  {
    if (with_elem->dependency_map & with_elem->get_elem_map())
      with_elem->is_recursive= true;
  }   
  for (With_element *with_elem= first_elem;
       with_elem != NULL;
       with_elem= with_elem->next_elem)
  {
    if (with_elem->is_recursive)
    {  
      my_error(ER_RECURSIVE_QUERY_IN_WITH_CLAUSE, MYF(0),
               with_elem->query_name->str);
      return true;
    }
  }

  if (!with_recursive)
  {
    /* 
      For each with table T defined in this with clause check whether
      it is used in any definition that follows the definition of T.
    */
    for (With_element *with_elem= first_elem;
         with_elem != NULL;
         with_elem= with_elem->next_elem)
    {
      With_element *checked_elem= with_elem->next_elem;
      for (uint i = with_elem->number+1;
           i < elements;
           i++, checked_elem= checked_elem->next_elem)
      {
        if (with_elem->check_dependency_on(checked_elem))
        {
          my_error(ER_WRONG_ORDER_IN_WITH_CLAUSE, MYF(0),
                   with_elem->query_name->str, checked_elem->query_name->str);
          return true;
        }
      }
    }
  }
	
  dependencies_are_checked= true;
  return false;
}


/**
  @brief
    Check dependencies on the sibling with tables used in the given unit

  @param unit  The unit where the siblings are to be searched for

  @details
    The method recursively looks through all from lists encountered
    the given unit. If it finds a reference to a table that is
    defined in the same with clause to which this element belongs
    the method set the bit of dependency on this table in the
    dependency_map of this element.
*/

void With_element::check_dependencies_in_unit(st_select_lex_unit *unit)
{
  st_select_lex *sl= unit->first_select();
  for (; sl; sl= sl->next_select())
  {
    for (TABLE_LIST *tbl= sl->table_list.first; tbl; tbl= tbl->next_local)
    {
      if (!tbl->with)
        tbl->with= owner->find_table_def(tbl);
      if (!tbl->with && tbl->select_lex)
        tbl->with= tbl->select_lex->find_table_def_in_with_clauses(tbl);
      if (tbl->with && tbl->with->owner== this->owner)
        set_dependency_on(tbl->with);
    }
    st_select_lex_unit *inner_unit= sl->first_inner_unit();
    for (; inner_unit; inner_unit= inner_unit->next_unit())
      check_dependencies_in_unit(inner_unit);
  }
}


/**
  @brief
    Search for the definition of a table among the elements of this with clause
 
  @param table    The reference to the table that is looked for

  @details
    The function looks through the elements of this with clause trying to find
    the definition of the given table. When it encounters the element with
    the same query name as the table's name it returns this element. If no
    such definitions are found the function returns NULL.

  @retval
    found with element if the search succeeded
    NULL - otherwise
*/    

With_element *With_clause::find_table_def(TABLE_LIST *table)
{
  for (With_element *with_elem= first_elem; 
       with_elem != NULL;
       with_elem= with_elem->next_elem)
  {
    if (my_strcasecmp(system_charset_info, with_elem->query_name->str, table->table_name) == 0) 
    {
      return with_elem;
    }
  }
  return NULL;
}


/**
  @brief
    Perform context analysis for all unreferenced tables defined in with clause

  @param thd   The context of the statement containing this with clause

  @details
    For each unreferenced table T defined in this with clause the method
    calls the method With_element::prepare_unreferenced that performs
    context analysis of the element with the definition of T.

  @retval
    false   If context analysis does not report any error
    true    Otherwise
*/

bool With_clause::prepare_unreferenced_elements(THD *thd)
{
  for (With_element *with_elem= first_elem; 
       with_elem != NULL;
       with_elem= with_elem->next_elem)
  {
    if (!with_elem->is_referenced() && with_elem->prepare_unreferenced(thd))
      return true;
  }

  return false;
}


/**
  @brief
    Save the specification of the given with table as a string

  @param thd        The context of the statement containing this with element
  @param spec_start The beginning of the specification in the input string
  @param spec_end   The end of the specification in the input string

  @details
    The method creates for a string copy of the specification used in this element.
    The method is called when the element is parsed. The copy may be used to
    create clones of the specification whenever they are needed.

  @retval
    false   on success
    true    on failure
*/
  
bool With_element::set_unparsed_spec(THD *thd, char *spec_start, char *spec_end)
{
  unparsed_spec.length= spec_end - spec_start;
  unparsed_spec.str= (char*) thd->memdup(spec_start, unparsed_spec.length+1);
  unparsed_spec.str[unparsed_spec.length]= '\0';

  if (!unparsed_spec.str)
  {
    my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 
             static_cast<int>(unparsed_spec.length));
    return true;
  }
  return false;
}


/**
  @brief
   Create a clone of the specification for the given with table
    
  @param thd        The context of the statement containing this with element
  @param with_table The reference to the table defined in this element for which
                     the clone is created.

  @details
    The method creates a clone of the specification used in this element.
    The clone is created for the given reference to the table defined by
    this element.
    The clone is created when the string with the specification saved in
    unparsed_spec is fed into the parser as an input string. The parsing
    this string a unit object representing the specification is build.
    A chain of all table references occurred in the specification is also
    formed.
    The method includes the new unit and its sub-unit into hierarchy of
    the units of the main query. I also insert the constructed chain of the 
    table references into the chain of all table references of the main query.

  @note
    Clones is created only for not first references to tables defined in
    the with clause. They are necessary for merged specifications because
    the optimizer handles any such specification as independent on the others.
    When a table defined in the with clause is materialized in a temporary table
    one could do without specification clones. However in this case they
    are created as well, because currently different table references to a
    the same temporary table cannot share the same definition structure.

  @retval
     pointer to the built clone if succeeds
     NULL - otherwise
*/

st_select_lex_unit *With_element::clone_parsed_spec(THD *thd,
                                                    TABLE_LIST *with_table)
{
  LEX *lex;
  st_select_lex_unit *res= NULL; 
  Query_arena backup;
  Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);

  if (!(lex= (LEX*) new(thd->mem_root) st_lex_local))
  {
    if (arena)
      thd->restore_active_arena(arena, &backup);
    return res;
  }
  LEX *old_lex= thd->lex;
  thd->lex= lex;

  bool parse_status= false;
  Parser_state parser_state;
  TABLE_LIST *spec_tables;
  TABLE_LIST *spec_tables_tail;
  st_select_lex *with_select;

  if (parser_state.init(thd, unparsed_spec.str, unparsed_spec.length))
    goto err;
  lex_start(thd);
  with_select= &lex->select_lex;
  with_select->select_number= ++thd->select_number;
  parse_status= parse_sql(thd, &parser_state, 0);
  if (parse_status)
    goto err;
  spec_tables= lex->query_tables;
  spec_tables_tail= 0;
  for (TABLE_LIST *tbl= spec_tables;
       tbl;
       tbl= tbl->next_global)
  {
    tbl->grant.privilege= with_table->grant.privilege;
    spec_tables_tail= tbl;
  }
  if (spec_tables)
  {
    if (with_table->next_global)
    {
      spec_tables_tail->next_global= with_table->next_global;
      with_table->next_global->prev_global= &spec_tables_tail->next_global;
    }
    else
    {
      old_lex->query_tables_last= &spec_tables_tail->next_global;
    }
    spec_tables->prev_global= &with_table->next_global;
    with_table->next_global= spec_tables;
  }
  res= &lex->unit;
  
  lex->unit.include_down(with_table->select_lex);
  lex->unit.set_slave(with_select); 
  old_lex->all_selects_list=
    (st_select_lex*) (lex->all_selects_list->
		      insert_chain_before(
			(st_select_lex_node **) &(old_lex->all_selects_list),
                        with_select));
  lex_end(lex);
err:
  if (arena)
    thd->restore_active_arena(arena, &backup);
  thd->lex= old_lex;
  return res;
}


/**
  @brief
    Rename columns of the unit derived from the spec of this with element
  @param thd        The context of the statement containing the with element
  @param unit       The specification of the with element or its clone

  @details
    The method assumes that the parameter unit is either specification itself
    of this with element or a clone of this specification. The looks through
    the column list in this with element. It reports an error if the cardinality
    of this list differs from the cardinality of select lists in 'unit'.
    Otherwise it renames the columns  of the first select list and sets the flag
    unit->column_list_is_processed to true preventing renaming columns for the
    second time.

  @retval
    true   if an error was reported 
    false  otherwise
*/    

bool 
With_element::rename_columns_of_derived_unit(THD *thd, 
                                             st_select_lex_unit *unit)
{
  if (unit->columns_are_renamed)
    return false;

  st_select_lex *select= unit->first_select();

  if (column_list.elements)  //  The column list is optional
  {
    List_iterator_fast<Item> it(select->item_list);
    List_iterator_fast<LEX_STRING> nm(column_list);
    Item *item;
    LEX_STRING *name;

    if (column_list.elements != select->item_list.elements)
    {
      my_error(ER_WITH_COL_WRONG_LIST, MYF(0));
      return true;
    }
    /* Rename the columns of the first select in the unit */
    while ((item= it++, name= nm++))
    {
      item->set_name(thd, name->str, (uint) name->length, system_charset_info);
      item->is_autogenerated_name= false;
    }
  }

  make_valid_column_names(thd, select->item_list);

  unit->columns_are_renamed= true;

  return false;
}


/**
  @brief
    Perform context analysis the definition of an unreferenced table
     
  @param thd        The context of the statement containing this with element

  @details
    The method assumes that this with element contains the definition
    of a table that is not used anywhere. In this case one has to check
    that context conditions are met. 

  @retval
    true   if an error was reported 
    false  otherwise
*/    

bool With_element::prepare_unreferenced(THD *thd)
{
  bool rc= false;
  st_select_lex *first_sl= spec->first_select();

  /* Prevent name resolution for field references out of with elements */
  for (st_select_lex *sl= first_sl;
       sl;
       sl= sl->next_select())
    sl->context.outer_context= 0;

  thd->lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED;
  if (!spec->prepared &&
      (spec->prepare(thd, 0, 0) ||
       rename_columns_of_derived_unit(thd, spec) ||
       check_duplicate_names(thd, first_sl->item_list, 1)))
    rc= true;
 
  thd->lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED;
  return rc;
}


/**
   @brief
     Search for the definition of the given table referred in this select node

  @param table  reference to the table whose definition is searched for
     
  @details  
    The method looks for the definition the table whose reference is occurred
    in the FROM list of this select node. First it searches for it in the
    with clause attached to the unit this select node belongs to. If such a
    definition is not found there the embedding units are looked through.

  @retval
    pointer to the found definition if the search has been successful
    NULL -  otherwise
*/    

With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table)
{
  With_element *found= NULL;
  for (st_select_lex *sl= this;
       sl;
       sl= sl->master_unit()->outer_select())
  {
    With_clause *with_clause=sl->get_with_clause();
    if (with_clause && (found= with_clause->find_table_def(table)))
      return found; 
    /* Do not look for the table's definition beyond the scope of the view */
    if (sl->master_unit()->is_view)
      break;      
  }
  return found;
}


/**
   @brief
     Set the specifying unit in this reference to a with table  
     
  @details  
    The method assumes that the given element with_elem defines the table T
    this table reference refers to.
    If this is the first reference to T the method just sets its specification
    in the field 'derived' as the unit that yields T. Otherwise the method  
    first creates a clone specification and sets rather this clone in this field.
 
  @retval
    false   on success
    true    on failure
*/    

bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem)
{
  with= with_elem;
  if (!with_elem->is_referenced())
    derived= with_elem->spec;
  else 
  {
    if(!(derived= with_elem->clone_parsed_spec(thd, this)))
      return true;
    derived->with_element= with_elem;
  }
  with_elem->inc_references();
  return false;
}


/**
  @brief
    Print this with clause
  
  @param str         Where to print to
  @param query_type  The mode of printing 
     
  @details
    The method prints a string representation of this clause in the 
    string str. The parameter query_type specifies the mode of printing.
*/    

void With_clause::print(String *str, enum_query_type query_type)
{
  str->append(STRING_WITH_LEN("WITH "));
  if (with_recursive)
    str->append(STRING_WITH_LEN("RECURSIVE "));
  for (With_element *with_elem= first_elem;
       with_elem != NULL;
       with_elem= with_elem->next_elem)
  {
    with_elem->print(str, query_type);
    if (with_elem != first_elem)
      str->append(", ");
  }
}


/**
   @brief
     Print this with element
  
  @param str         Where to print to
  @param query_type  The mode of printing 
     
  @details
    The method prints a string representation of this with element in the 
    string str. The parameter query_type specifies the mode of printing.
*/

void With_element::print(String *str, enum_query_type query_type)
{
  str->append(query_name);
  str->append(STRING_WITH_LEN(" AS "));
  str->append('(');
  spec->print(str, query_type);
  str->append(')');
}