-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathsql_view.cc
2075 lines (1772 loc) · 72.3 KB
/
sql_view.cc
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
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (c) 2004, 2025, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is designed to work with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have either included with
the program or referenced in the documentation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "sql/sql_view.h"
#include <sys/types.h>
#include <climits>
#include <cstdio>
#include <cstring>
#include <utility>
#include "lex_string.h"
#include "m_string.h"
#include "mem_root_deque.h" // mem_root_deque
#include "my_alloc.h" // operator new
#include "my_base.h"
#include "my_dbug.h"
#include "my_inttypes.h"
#include "my_sqlcommand.h"
#include "my_sys.h"
#include "mysql/mysql_lex_string.h"
#include "mysql/psi/mysql_mutex.h"
#include "mysql/strings/m_ctype.h"
#include "mysql_com.h"
#include "mysqld_error.h"
#include "sql/auth/auth_acls.h"
#include "sql/auth/auth_common.h" // CREATE_VIEW_ACL
#include "sql/auth/sql_authorization.h" // check_valid_definer
#include "sql/auth/sql_security_ctx.h"
#include "sql/binlog.h" // mysql_bin_log
#include "sql/dd/cache/dictionary_client.h"
#include "sql/dd/dd.h" // dd::get_dictionary
#include "sql/dd/dd_schema.h" // dd::schema_exists
#include "sql/dd/dd_view.h" // dd::create_view
#include "sql/dd/dictionary.h" // dd::Dictionary
#include "sql/dd/string_type.h" // String_type
#include "sql/dd/types/abstract_table.h"
#include "sql/dd/types/view.h" // View
#include "sql/dd_sql_view.h" // update_referencing_views_metadata
#include "sql/derror.h" // ER_THD
#include "sql/enum_query_type.h"
#include "sql/error_handler.h" // Internal_error_handler
#include "sql/field.h"
#include "sql/item.h"
#include "sql/key.h"
#include "sql/mdl.h"
#include "sql/mysqld.h" // stage_end reg_ext key_file_frm
#include "sql/opt_trace.h" // opt_trace_disable_if_no_view_access
#include "sql/parse_tree_node_base.h"
#include "sql/parser_yystype.h" // Create_col_name_list
#include "sql/query_options.h"
#include "sql/sp_cache.h" // sp_cache_invalidate
#include "sql/sql_base.h" // get_table_def_key
#include "sql/sql_class.h" // THD
#include "sql/sql_const.h"
#include "sql/sql_error.h"
#include "sql/sql_lex.h"
#include "sql/sql_list.h"
#include "sql/sql_parse.h" // create_default_definer
#include "sql/sql_show.h" // append_identifier_*
#include "sql/sql_table.h" // write_bin_log
#include "sql/strfunc.h"
#include "sql/system_variables.h"
#include "sql/table.h"
#include "sql/thd_raii.h"
#include "sql/transaction.h"
#include "sql_string.h"
#include "string_with_len.h"
#include "thr_lock.h"
namespace dd {
class Schema;
} // namespace dd
/*
Make a unique name for an anonymous view column
SYNOPSIS
target reference to the item for which a new name has to be made
item_list list of items within which we should check uniqueness of
the created name
last_element the last element of the list above
NOTE
Unique names are generated by adding a fixed prefix to the old name of the
column. In case the name that was created this way already exists, we
add a numeric postfix to its end (i.e. "1") and increase the number
until the name becomes unique. If the generated name is longer than
NAME_CHAR_LEN code points, it is truncated.
We truncate to the nearest code point, which means that in rare
cases we may truncate in the middle of some grapheme cluster, but
the result is still treated as a valid name by the data dictionary.
*/
static void make_unique_view_field_name(Item *target,
const mem_root_deque<Item *> &item_list,
Item *last_element) {
const char *name = (target->orig_name.is_set() ? target->orig_name.ptr()
: target->item_name.ptr());
size_t name_len;
uint attempt;
char buff[NAME_LEN + 1];
for (attempt = 0;; attempt++) {
bool ok = true;
if (attempt)
name_len =
snprintf(buff, NAME_LEN, SYNTHETIC_FIELD_NAME "%d_%s", attempt, name);
else
name_len = snprintf(buff, NAME_LEN, SYNTHETIC_FIELD_NAME "%s", name);
size_t name_len_mb = system_charset_info->cset->numchars(
system_charset_info, buff, buff + name_len);
if (name_len_mb > NAME_CHAR_LEN) {
size_t num_bytes = system_charset_info->cset->charpos(
system_charset_info, buff, buff + name_len, NAME_CHAR_LEN);
buff[num_bytes] = '\0';
name_len = num_bytes;
}
for (Item *itc : VisibleFields(item_list)) {
if (itc != target && itc->item_name.eq(buff)) {
ok = false;
break;
}
if (itc == last_element) break;
}
if (ok) break;
}
target->orig_name = target->item_name;
target->item_name.copy(buff, name_len);
}
/**
When creating a derived table, check if duplicate column names are present,
and possibly generate unique names instead.
@param column_names User-provided list of column names, NULL if none
@param item_list SELECT list of underlying query expression
@param gen_unique_view_name See description.
- If a list of column names has been provided: it is simply searched for
duplicates (which cause an error).
- otherwise, column names are derived from the underlying query expression's
SELECT list elements; if two of those elements have duplicate autogenerated
names:
* if gen_unique_view_name is false: error
* it it is true: we generate a unique name using
make_unique_view_field_name()
@returns true if error.
*/
bool check_duplicate_names(const Create_col_name_list *column_names,
const mem_root_deque<Item *> &item_list,
bool gen_unique_view_name) {
DBUG_TRACE;
if (column_names) {
const uint count = column_names->size();
if (count != CountVisibleFields(item_list)) {
my_error(ER_VIEW_WRONG_LIST, MYF(0));
return true;
}
for (uint i = 0; i < count; ++i)
for (uint j = i + 1; j < count; ++j) {
if (!my_strcasecmp(system_charset_info, (*column_names)[i].str,
(*column_names)[j].str)) {
my_error(ER_DUP_FIELDNAME, MYF(0), (*column_names)[i].str);
return true;
}
}
return false;
}
for (Item *item : VisibleFields(item_list)) {
/* treat underlying fields like set by user names */
if (item->real_item()->type() == Item::FIELD_ITEM)
item->item_name.set_autogenerated(false);
for (Item *check : VisibleFields(item_list)) {
if (item == check) break;
if (item->item_name.eq(check->item_name)) {
if (!gen_unique_view_name) {
my_error(ER_DUP_FIELDNAME, MYF(0), item->item_name.ptr());
return true;
}
if (item->item_name.is_autogenerated())
make_unique_view_field_name(item, item_list, item);
else if (check->item_name.is_autogenerated())
make_unique_view_field_name(check, item_list, item);
else {
my_error(ER_DUP_FIELDNAME, MYF(0), item->item_name.ptr());
return true;
}
}
}
}
return false;
}
/**
Check if auto generated column names are conforming and
possibly generate a conforming name for them if not.
@param lex LEX for this thread.
*/
void make_valid_column_names(LEX *lex) {
size_t name_len;
char buff[NAME_LEN];
uint column_no = 0;
for (Query_block *sl = lex->query_block; sl; sl = sl->next_query_block()) {
for (Item *item : sl->visible_fields()) {
++column_no;
if (!item->item_name.is_autogenerated() ||
!check_column_name(item->item_name))
continue;
name_len = snprintf(buff, NAME_LEN, SYNTHETIC_FIELD_NAME "%u", column_no);
item->orig_name = item->item_name;
item->item_name.copy(buff, name_len);
}
}
}
/*
Fill defined view parts
SYNOPSIS
fill_defined_view_parts()
thd current thread.
view view to operate on
DESCRIPTION
This function will initialize the parts of the view
definition that are not specified in ALTER VIEW
to their values from CREATE VIEW.
The view must be opened to get its definition.
We use a copy of the view when opening because we want
to preserve the original view instance.
RETURN VALUE
true can't open table
false success
*/
static bool fill_defined_view_parts(THD *thd, Table_ref *view) {
const char *cache_key;
size_t cache_key_length = get_table_def_key(view, &cache_key);
Table_ref decoy = *view;
/*
It's not clear what the above assignment actually wants to
accomplish. What we do know is that it does *not* want to copy the MDL
request, so we overwrite it with an uninitialized request.
*/
decoy.mdl_request = MDL_request();
mysql_mutex_lock(&LOCK_open);
TABLE_SHARE *share;
if (!(share = get_table_share(thd, view->db, view->table_name, cache_key,
cache_key_length, true))) {
mysql_mutex_unlock(&LOCK_open);
return true;
}
if (!share->is_view) {
my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW");
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
return true;
}
bool view_open_result = open_and_read_view(thd, share, &decoy);
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
if (view_open_result) return true;
LEX *lex = thd->lex;
if (!lex->definer) {
view->definer.host = decoy.definer.host;
view->definer.user = decoy.definer.user;
lex->definer = &view->definer;
}
if (lex->create_view_algorithm == VIEW_ALGORITHM_UNDEFINED)
lex->create_view_algorithm = (uint8)decoy.algorithm;
if (lex->create_view_suid == VIEW_SUID_DEFAULT)
lex->create_view_suid =
decoy.view_suid ? VIEW_SUID_DEFINER : VIEW_SUID_INVOKER;
return false;
}
/**
@brief CREATE VIEW privileges pre-check.
@param thd thread handler
@param tables tables used in the view
@param view views to create
@param mode VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
@retval false Operation was a success.
@retval true An error occurred.
*/
bool create_view_precheck(THD *thd, Table_ref *tables, Table_ref *view,
enum_view_create_mode mode) {
LEX *const lex = thd->lex;
/* first table in list is target VIEW name => cut off it */
Query_block *const query_block = lex->query_block;
bool res = true;
DBUG_TRACE;
/*
Privilege check for view creation:
- user has CREATE VIEW privilege on view table
- user has DROP privilege in case of ALTER VIEW or CREATE OR REPLACE
VIEW
- user has some (SELECT/UPDATE/INSERT/DELETE) privileges on columns of
underlying tables used on top of SELECT list (because it can be
(theoretically) updated, so it is enough to have UPDATE privilege on
them, for example)
- user has SELECT privilege on columns used in expressions of VIEW select
- for columns of underly tables used on top of SELECT list also will be
checked that we have not more privileges on correspondent column of view
table (i.e. user will not get some privileges by view creation)
*/
// Allow creation of views on information_schema only during bootstrap
if (!is_infoschema_db(view->db)) {
if ((check_access(thd, CREATE_VIEW_ACL, view->db, &view->grant.privilege,
&view->grant.m_internal, false, false) ||
check_grant(thd, CREATE_VIEW_ACL, view, false, 1, false)) ||
(mode != enum_view_create_mode::VIEW_CREATE_NEW &&
(check_access(thd, DROP_ACL, view->db, &view->grant.privilege,
&view->grant.m_internal, false, false) ||
check_grant(thd, DROP_ACL, view, false, 1, false))))
goto err;
}
for (Table_ref *tbl = tables; tbl; tbl = tbl->next_global) {
/*
Ensure that we have some privileges on this table, stricter checks will
be performed for each referenced column during resolving.
*/
if (tbl->is_internal()) {
// Optimizer internal tables have no ACL entries
tbl->set_privileges(SELECT_ACL);
continue;
}
if (check_some_access(thd, VIEW_ANY_ACL, tbl)) {
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), "ANY",
thd->security_context()->priv_user().str,
thd->security_context()->priv_host().str, tbl->table_name);
goto err;
}
/*
Make sure that current table privileges are loaded to the
TABLE::grant field. tbl->table_name will be correct name of table
because VIEWs are not opened yet.
*/
fill_effective_table_privileges(thd, &tbl->grant, tbl->db,
tbl->get_table_name());
}
/*
Mark fields for special privilege check ("any" privilege)
*/
for (Query_block *sl = query_block; sl; sl = sl->next_query_block()) {
for (Item *item : sl->visible_fields()) {
Item_field *field;
if ((field = item->field_for_view_update())) {
/*
any_privileges may be reset later by the Item_field::set_field
method in case of a system temporary table.
*/
field->any_privileges = true;
}
}
}
res = false;
err:
return res || thd->is_error();
}
/**
@brief Creating/altering VIEW procedure
Atomicity:
The operation to create, alter and create_or_replace a view is
atomic/crash-safe.
Changes to the Data-dictionary and writing event to binlog are
part of the same transaction. All the changes are done as part
of the same transaction or do not have any side effects on the
operation failure. Data-dictionary and table definition caches
are in sync with operation state. Cache do not contain any
stale/incorrect data in case of failure.
In case of crash, there won't be any discrepancy between
the data-dictionary table and the binary log.
@param thd thread handler
@param views views to create
@param mode VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
@note This function handles both create and alter view commands.
@retval false Operation was a success.
@retval true An error occurred.
*/
bool mysql_create_view(THD *thd, Table_ref *views, enum_view_create_mode mode) {
LEX *lex = thd->lex;
bool link_to_local;
/* first table in list is target VIEW name => cut off it */
Table_ref *view = lex->unlink_first_table(&link_to_local);
Table_ref *tables = lex->query_tables;
Table_ref *tbl;
Query_block *const query_block = lex->query_block;
Query_block *sl;
Query_expression *const unit = lex->unit;
bool res = false;
bool schema_exists = false;
bool use_existing_view = false;
DBUG_TRACE;
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
/* This is ensured in the parser. */
assert(!lex->result && !lex->param_list.elements);
/*
We can't allow taking exclusive meta-data locks of unlocked view under
LOCK TABLES since this might lead to deadlock. Since at the moment we
can't really lock view with LOCK TABLES we simply prohibit creation/
alteration of views under LOCK TABLES.
*/
if (thd->locked_tables_mode) {
my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
res = true;
goto err;
}
if (create_view_precheck(thd, tables, view, mode)) {
res = true;
goto err;
}
lex->link_first_table_back(view, link_to_local);
view->open_type = OT_BASE_ONLY;
/*
No pre-opening of temporary tables is possible since must
wait until Table_ref::open_type is set. So we have to open
them here instead.
*/
if (open_temporary_tables(thd, lex->query_tables)) {
view = lex->unlink_first_table(&link_to_local);
res = true;
goto err;
}
/* Not required to lock any tables. */
if (open_tables_for_query(thd, lex->query_tables, 0)) {
view = lex->unlink_first_table(&link_to_local);
res = true;
goto err;
}
view = lex->unlink_first_table(&link_to_local);
/*
Checking the existence of the database in which the view is to be created.
Errors will be reported in dd::schema_exists().
*/
if (dd::schema_exists(thd, view->db, &schema_exists)) {
res = true;
goto err;
} else if (!schema_exists) {
my_error(ER_BAD_DB_ERROR, MYF(0), view->db);
res = true;
goto err;
}
if (mode == enum_view_create_mode::VIEW_ALTER &&
fill_defined_view_parts(thd, view)) {
res = true;
goto err;
}
sp_cache_invalidate();
if (!lex->definer) {
/*
DEFINER-clause is missing; we have to create default definer in
persistent arena to be PS/SP friendly.
If this is an ALTER VIEW then the current user should be set as
the definer.
*/
Prepared_stmt_arena_holder ps_arena_holder(thd);
lex->definer = create_default_definer(thd);
if (!lex->definer) goto err;
}
if (check_valid_definer(thd, lex->definer)) {
res = true;
goto err;
}
/*
check that tables are not temporary and this VIEW do not used in query
(it is possible with ALTERing VIEW).
open_and_lock_tables can change the value of tables,
e.g. it may happen if before the function call tables was equal to 0.
*/
for (tbl = lex->query_tables; tbl; tbl = tbl->next_global) {
/* is this table view and the same view which we creates now? */
if (tbl->is_view() && strcmp(tbl->db, view->db) == 0 &&
strcmp(tbl->table_name, view->table_name) == 0) {
my_error(ER_NO_SUCH_TABLE, MYF(0), tbl->db, tbl->table_name);
res = true;
goto err;
}
/*
tbl->table can be NULL when tbl is a placeholder for a view
that is indirectly referenced via a stored function from the
view being created. We don't check these indirectly
referenced views in CREATE VIEW so they don't have table
object.
*/
if (tbl->table) {
/*
is this table temporary and is not a derived table, a view, a recursive
reference, a table function or a schema table?
*/
if (tbl->table->s->tmp_table != NO_TMP_TABLE && !tbl->is_placeholder()) {
my_error(ER_VIEW_SELECT_TMPTABLE, MYF(0), tbl->alias);
res = true;
goto err;
}
}
}
/* prepare select to resolve all fields */
lex->context_analysis_only |= CONTEXT_ANALYSIS_ONLY_VIEW;
if (!unit->is_prepared()) {
Prepared_stmt_arena_holder ps_arena_holder(thd);
/*
@todo - the following code is duplicated in mysql_test_create_view.
ensure that we have a single preparation function for create view.
*/
if (unit->prepare(thd, nullptr, nullptr, 0, 0)) {
/*
some errors from prepare are reported to user, if is not then
it will be checked after err: label
*/
res = true;
goto err;
}
/* Check if the auto generated column names are conforming. */
make_valid_column_names(lex);
/*
Only column names of the first query block should be checked for
duplication; any further UNION-ed part isn't used for determining
names of the view's columns.
*/
if (check_duplicate_names(view->derived_column_names(), query_block->fields,
true)) {
res = true;
goto err;
}
/*
Make sure the view doesn't have so many columns that we hit the
64k header limit if the view is materialized as a MyISAM table.
*/
if (query_block->fields.size() > MAX_FIELDS) {
my_error(ER_TOO_MANY_FIELDS, MYF(0));
res = true;
goto err;
}
} else {
lex->restore_cmd_properties();
bind_fields(thd->stmt_arena->item_list());
}
/*
Compare/check grants on view with grants of underlying tables
*/
if (view->is_internal())
view->set_privileges(SELECT_ACL);
else
fill_effective_table_privileges(thd, &view->grant, view->db,
view->get_table_name());
/*
Make sure that the current user does not have more column-level privileges
on the newly created view than he/she does on the underlying
tables. E.g. it must not be so that the user has UPDATE privileges on a
view column of he/she doesn't have it on the underlying table's
corresponding column. In that case, return an error for CREATE VIEW.
*/
{
Item *report_item = nullptr;
/*
This will hold the intersection of the privileges on all columns in the
view.
*/
Access_bitmask final_priv = VIEW_ANY_ACL;
for (sl = query_block; sl; sl = sl->next_query_block()) {
assert(view->db); /* Must be set in the parser */
for (Item *item : sl->visible_fields()) {
Item_field *fld = item->field_for_view_update();
Access_bitmask priv =
(get_column_grant(thd, &view->grant, view->db, view->table_name,
item->item_name.ptr()) &
VIEW_ANY_ACL);
if (fld && !fld->field->table->s->tmp_table) {
final_priv &= fld->have_privileges;
if (~fld->have_privileges & priv) report_item = item;
}
}
}
if (!final_priv && report_item) {
my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), "create view",
thd->security_context()->priv_user().str,
thd->security_context()->priv_host().str,
report_item->item_name.ptr(), view->table_name);
res = true;
goto err;
}
}
if ((res = mysql_register_view(thd, view, mode, &use_existing_view)))
goto err_with_rollback;
if (!use_existing_view) {
/*
View TABLE_SHARE must be removed from the table definition cache in order
to make ALTER VIEW work properly. Otherwise, we would not be able to
detect meta-data changes after ALTER VIEW.
*/
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, view->db, view->table_name, false);
// Update metadata of views referencing "view".
Uncommitted_tables_guard uncommited_tables(thd);
uncommited_tables.add_table(view);
if ((res = update_referencing_views_metadata(thd, view, false,
&uncommited_tables)))
goto err_with_rollback;
}
// Binlog CREATE/ALTER/CREATE OR REPLACE event.
if (mysql_bin_log.is_open() &&
(thd->variables.option_bits & OPTION_BIN_LOG)) {
String buff;
const LEX_CSTRING command[3] = {{STRING_WITH_LEN("CREATE ")},
{STRING_WITH_LEN("ALTER ")},
{STRING_WITH_LEN("CREATE OR REPLACE ")}};
buff.append(command[static_cast<int>(thd->lex->create_view_mode)].str,
command[static_cast<int>(thd->lex->create_view_mode)].length);
view_store_options(thd, views, &buff);
buff.append(STRING_WITH_LEN("VIEW "));
if ((mode == enum_view_create_mode::VIEW_CREATE_NEW) &&
(lex->create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)) {
buff.append(STRING_WITH_LEN("IF NOT EXISTS "));
}
/* Test if user supplied a db (ie: we did not use thd->db) */
if (views->db && views->db[0] &&
(thd->db().str == nullptr || strcmp(views->db, thd->db().str))) {
append_identifier(thd, &buff, views->db, views->db_length);
buff.append('.');
}
append_identifier(thd, &buff, views->table_name, views->table_name_length);
if (view->derived_column_names()) {
int i = 0;
for (auto name : *view->derived_column_names()) {
buff.append(i++ ? ", " : "(");
append_identifier(thd, &buff, name.str, name.length);
}
buff.append(')');
}
buff.append(STRING_WITH_LEN(" AS "));
buff.append(views->source.str, views->source.length);
int errcode = query_error_code(thd, true);
thd->add_to_binlog_accessed_dbs(views->db);
if ((res =
thd->binlog_query(THD::STMT_QUERY_TYPE, buff.ptr(), buff.length(),
!use_existing_view, false, false, errcode)))
goto err_with_rollback;
}
// Commit changes to the data-dictionary and binary log.
res = DBUG_EVALUATE_IF("simulate_create_view_failure", true, false) ||
trans_commit_stmt(thd) || trans_commit(thd);
if (res) goto err_with_rollback;
if (use_existing_view) {
// This is a CREATE VIEW IF NOT EXISTS statement dealing with
// an existing view.
push_warning_printf(thd, Sql_condition::SL_NOTE, ER_TABLE_EXISTS_ERROR,
ER_THD(thd, ER_TABLE_EXISTS_ERROR), views->table_name);
}
my_ok(thd);
lex->link_first_table_back(view, link_to_local);
return false;
err_with_rollback:
DBUG_EXECUTE_IF("simulate_create_view_failure",
my_error(ER_UNKNOWN_ERROR, MYF(0)););
trans_rollback_stmt(thd);
/*
Full rollback in case we have THD::transaction_rollback_request
and to synchronize DD state in cache and on disk (as statement
rollback doesn't clear DD cache of modified uncommitted objects).
*/
trans_rollback(thd);
err:
THD_STAGE_INFO(thd, stage_end);
lex->link_first_table_back(view, link_to_local);
unit->cleanup(true);
return res || thd->is_error();
}
/*
Check if view is updatable.
@param thd Thread Handle.
@param view View description.
@retval true View is updatable.
@retval false Otherwise.
*/
bool is_updatable_view(THD *thd, Table_ref *view) {
bool updatable_view = false;
LEX *lex = thd->lex;
/*
A view can be merged if it is technically possible and if the user didn't
ask that we create a temporary table instead.
*/
bool can_be_merged =
lex->unit->is_mergeable() && view->algorithm != VIEW_ALGORITHM_TEMPTABLE;
if (!dd::get_dictionary()->is_system_view_name(view->db, view->table_name) &&
(updatable_view = can_be_merged)) {
/// @see Query_block::merge_derived()
bool updatable = false;
bool outer_joined = false;
for (Table_ref *tbl = lex->query_block->get_table_list(); tbl;
tbl = tbl->next_local) {
updatable |=
!((tbl->is_view() && !tbl->updatable_view) || tbl->schema_table);
outer_joined |= tbl->is_inner_table_of_outer_join();
}
updatable &= !outer_joined;
if (updatable) {
// check that at least one column in view is updatable.
bool view_has_updatable_column = false;
for (Item *item : lex->query_block->visible_fields()) {
Item_field *item_field = item->field_for_view_update();
if (item_field && !item_field->m_table_ref->schema_table) {
view_has_updatable_column = true;
break;
}
}
updatable &= view_has_updatable_column;
}
if (!updatable) updatable_view = false;
}
/*
Check that table of main select do not used in subqueries.
This test can catch only very simple cases of such non-updateable views,
all other will be detected before updating commands execution.
(it is more optimisation then real check)
NOTE: this skip cases of using table via VIEWs, joined VIEWs, VIEWs with
UNION
*/
if (updatable_view &&
!lex->query_block->master_query_expression()->is_set_operation() &&
!(lex->query_block->get_table_list())->next_local &&
find_table_in_global_list(lex->query_tables->next_global,
lex->query_tables->db,
lex->query_tables->table_name)) {
updatable_view = false;
}
return updatable_view;
}
/**
Register view by writing its definition to the data-dictionary.
@param thd Thread handler.
@param view View description
@param mode VIEW_CREATE_NEW, VIEW_ALTER or
VIEW_CREATE_OR_REPLACE.
@param[out] use_existing_view Set to true when IF NOT EXISTS clause used
to create a new view, but a view/table with
the same name already exists in the schema.
@note The caller must rollback both statement and transaction on failure,
before any further accesses to DD. This is because such a failure
might be caused by a deadlock, which requires rollback before any
other operations on SE (including reads using attachable transactions)
can be done.
@retval false OK
@retval true Error
*/
bool mysql_register_view(THD *thd, Table_ref *view, enum_view_create_mode mode,
bool *use_existing_view) {
/*
View definition query -- a SELECT statement that fully defines view. It
is generated from the Item-tree built from the original (specified by
the user) query. The idea is that generated query should eliminates all
ambiguities and fix view structure at CREATE-time (once for all).
Item::print() virtual operation is used to generate view definition
query.
INFORMATION_SCHEMA query (IS query) -- a SQL statement describing a
view that is shown in INFORMATION_SCHEMA. Basically, it is 'view
definition query' with text literals converted to UTF8 and without
character set introducers.
For example:
Let's suppose we have:
CREATE TABLE t1(a INT, b INT);
User specified query:
CREATE VIEW v1(x, y) AS SELECT * FROM t1;
Generated query:
SELECT a AS x, b AS y FROM t1;
IS query:
SELECT a AS x, b AS y FROM t1;
View definition query is stored in the client character set.
*/
char view_query_buff[4096];
String view_query(view_query_buff, sizeof(view_query_buff), thd->charset());
char is_query_buff[4096];
String is_query(is_query_buff, sizeof(is_query_buff), system_charset_info);
DBUG_TRACE;
*use_existing_view = false;
/*
A view can be merged if it is technically possible and if the user didn't
ask that we create a temporary table instead.
*/
LEX *lex = thd->lex;
const bool can_be_merged =
lex->unit->is_mergeable() &&
lex->create_view_algorithm != VIEW_ALGORITHM_TEMPTABLE;
if (can_be_merged) {
for (ORDER *order = lex->query_block->order_list.first; order;
order = order->next)
order->used_alias = nullptr; /// @see Item::print_for_order()
}
/* Generate view definition and IS queries. */
view_query.length(0);
is_query.length(0);
{
// Turn off ANSI_QUOTES and other SQL modes which affect printing of
// view definition.
Sql_mode_parse_guard parse_guard(thd);
lex->unit->print(
thd, &view_query,
enum_query_type(QT_TO_ARGUMENT_CHARSET | QT_HIDE_ROLLUP_FUNCTIONS));
lex->unit->print(
thd, &is_query,
enum_query_type(QT_TO_SYSTEM_CHARSET | QT_WITHOUT_INTRODUCERS));
}
DBUG_PRINT("info",
("View: %*.s", (int)view_query.length(), view_query.ptr()));
/* fill structure (NOTE: Table_ref::source will be removed) */
view->source = thd->lex->create_view_query_block;
if (lex_string_strmake(thd->mem_root, &view->select_stmt, view_query.ptr(),
view_query.length())) {
my_error(ER_OUT_OF_RESOURCES, MYF(0));
return true;
}
if (lex->create_view_algorithm == VIEW_ALGORITHM_MERGE && !can_be_merged) {
push_warning(thd, Sql_condition::SL_WARNING, ER_WARN_VIEW_MERGE,
ER_THD(thd, ER_WARN_VIEW_MERGE));
lex->create_view_algorithm = VIEW_ALGORITHM_UNDEFINED;
}
view->algorithm = lex->create_view_algorithm;
view->definer.user = lex->definer->user;
view->definer.host = lex->definer->host;
view->view_suid = lex->create_view_suid;
view->with_check = lex->create_view_check;
view->updatable_view = is_updatable_view(thd, view);
/* init timestamp */
if (!view->timestamp.str) view->timestamp.str = view->timestamp_buffer;
/* check old definition */
bool update_view = false;
const dd::Abstract_table *at = nullptr;
if (thd->dd_client()->acquire(view->db, view->table_name, &at)) return true;
if (at != nullptr) {
if (mode == enum_view_create_mode::VIEW_CREATE_NEW) {
*use_existing_view =
lex->create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS;
if (!*use_existing_view) {
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), view->alias);
return true;
}
} else {
if (at->type() != dd::enum_table_type::USER_VIEW &&
at->type() != dd::enum_table_type::SYSTEM_VIEW) {
my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW");
return true;
}
update_view = true;
/*
TODO: read dependence list, too, to process cascade/restrict
TODO: special cascade/restrict procedure for alter?
*/
}
} else {
if (mode == enum_view_create_mode::VIEW_ALTER) {
my_error(ER_NO_SUCH_TABLE, MYF(0), view->db, view->alias);
return true;
}
}
/* Initialize view creation context from the environment. */
view->view_creation_ctx = View_creation_ctx::create(thd);
/*
Set LEX_STRING attributes in view-structure for parser to create
frm-file.
*/
lex_cstring_set(&view->view_client_cs_name,