-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathsp.cc
2967 lines (2520 loc) · 101 KB
/
sp.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) 2002, 2024, 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/sp.h"
#include <string.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <new>
#include <utility>
#include <vector>
#include "lex_string.h"
#include "m_string.h"
#include "my_alloc.h"
#include "my_base.h"
#include "my_dbug.h"
#include "my_psi_config.h"
#include "my_sqlcommand.h"
#include "my_sys.h"
#include "mysql/components/services/bits/psi_bits.h"
#include "mysql/components/services/bits/psi_statement_bits.h"
#include "mysql/components/services/log_builtins.h"
#include "mysql/components/services/log_shared.h"
#include "mysql/my_loglevel.h"
#include "mysql/psi/mysql_sp.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" // check_some_routine_access
#include "sql/auth/sql_security_ctx.h"
#include "sql/binlog.h" // mysql_bin_log
#include "sql/dd/cache/dictionary_client.h" // dd::cache::Dictionary_client
#include "sql/dd/dd_routine.h" // dd routine methods.
#include "sql/dd/dd_utility.h" // dd::normalize_string
#include "sql/dd/string_type.h"
#include "sql/dd/types/function.h"
#include "sql/dd/types/library.h"
#include "sql/dd/types/procedure.h"
#include "sql/dd/types/routine.h"
#include "sql/dd/types/schema.h"
#include "sql/dd/types/trigger.h" //name_collation
#include "sql/dd_sp.h" // prepare_sp_chistics_from_dd_routine
#include "sql/dd_sql_view.h" // update_referencing_views_metadata
#include "sql/dd_table_share.h" // dd_get_mysql_charset
#include "sql/debug_sync.h" // DEBUG_SYNC
#include "sql/error_handler.h" // Internal_error_handler
#include "sql/field.h"
#include "sql/handler.h"
#include "sql/lock.h" // lock_object_name
#include "sql/log_event.h" // append_query_string
#include "sql/mdl.h"
#include "sql/mysqld.h" // trust_function_creators
#include "sql/protocol.h"
#include "sql/psi_memory_key.h" // key_memory_sp_head_main_root
#include "sql/set_var.h"
#include "sql/sp_cache.h" // sp_cache_invalidate
#include "sql/sp_head.h" // Stored_program_creation_ctx
#include "sql/sp_pcontext.h" // sp_pcontext
#include "sql/sql_class.h"
#include "sql/sql_const.h"
#include "sql/sql_db.h" // get_default_db_collation
#include "sql/sql_digest_stream.h"
#include "sql/sql_error.h"
#include "sql/sql_lex.h" // enum_sp_type
#include "sql/sql_list.h"
#include "sql/sql_parse.h" // parse_sql
#include "sql/sql_show.h" // append_identifier_*
#include "sql/sql_table.h" // write_bin_log
#include "sql/strfunc.h" // lex_string_strmake
#include "sql/system_variables.h"
#include "sql/table.h"
#include "sql/thd_raii.h"
#include "sql/thr_malloc.h"
#include "sql/transaction.h"
#include "sql/transaction_info.h"
#include "sql_string.h"
#include "string_with_len.h"
#include "template_utils.h"
class sp_rcontext;
/* Used in error handling only */
#define SP_TYPE_STRING(type) \
((type) == enum_sp_type::FUNCTION ? "FUNCTION" \
: (type) == enum_sp_type::PROCEDURE ? "PROCEDURE" \
: (type) == enum_sp_type::LIBRARY ? "LIBRARY" \
: "UNKNOWN")
static bool create_string(
THD *thd, String *buf, enum_sp_type sp_type, const char *db, size_t dblen,
const char *name, size_t namelen, const char *params, size_t paramslen,
const char *returns, size_t returnslen, const char *body, size_t bodylen,
st_sp_chistics *chistics, const LEX_CSTRING &definer_user,
const LEX_CSTRING &definer_host, sql_mode_t sql_mode, bool if_not_exists);
static bool create_stored_procedure_and_function_string(
THD *thd, String *buf, enum_sp_type type, const char *db, size_t dblen,
const char *name, size_t namelen, const char *params, size_t paramslen,
const char *returns, size_t returnslen, const char *body, size_t bodylen,
st_sp_chistics *chistics, const LEX_CSTRING &definer_user,
const LEX_CSTRING &definer_host, bool if_not_exists, bool is_sql,
const char *dollar_quote, size_t dollar_quote_len, size_t language_length);
static bool create_library_string(THD *thd, String *buf, const char *db,
size_t dblen, const char *name,
size_t namelen, const char *body,
size_t bodylen, st_sp_chistics *chistics,
bool if_not_exists, const char *dollar_quote,
size_t dollar_quote_len,
size_t language_length);
/**************************************************************************
Fetch stored routines and events creation_ctx for upgrade.
**************************************************************************/
bool load_charset(MEM_ROOT *mem_root, Field *field, const CHARSET_INFO *dflt_cs,
const CHARSET_INFO **cs) {
String cs_name;
if (get_field(mem_root, field, &cs_name)) {
*cs = dflt_cs;
return true;
}
*cs = get_charset_by_csname(cs_name.c_ptr(), MY_CS_PRIMARY, MYF(0));
if (*cs == nullptr) {
*cs = dflt_cs;
return true;
}
return false;
}
/*************************************************************************/
bool load_collation(MEM_ROOT *mem_root, Field *field,
const CHARSET_INFO *dflt_cl, const CHARSET_INFO **cl) {
String cl_name;
if (get_field(mem_root, field, &cl_name)) {
*cl = dflt_cl;
return true;
}
*cl = get_charset_by_name(cl_name.c_ptr(), MYF(0));
if (*cl == nullptr) {
*cl = dflt_cl;
return true;
}
return false;
}
/**************************************************************************
Stored_routine_creation_ctx implementation.
**************************************************************************/
Stored_routine_creation_ctx *
Stored_routine_creation_ctx::create_routine_creation_ctx(
const dd::Routine *routine) {
/* Load character set/collation attributes. */
const CHARSET_INFO *client_cs =
dd_get_mysql_charset(routine->client_collation_id());
const CHARSET_INFO *connection_cl =
dd_get_mysql_charset(routine->connection_collation_id());
const CHARSET_INFO *db_cl =
dd_get_mysql_charset(routine->schema_collation_id());
assert(client_cs != nullptr);
assert(connection_cl != nullptr);
assert(db_cl != nullptr);
// Create the context.
return new (*THR_MALLOC)
Stored_routine_creation_ctx(client_cs, connection_cl, db_cl);
}
/*************************************************************************/
Stored_routine_creation_ctx *Stored_routine_creation_ctx::load_from_db(
THD *thd, const sp_name *name, TABLE *proc_tbl) {
// Load character set/collation attributes.
const CHARSET_INFO *client_cs;
const CHARSET_INFO *connection_cl;
const CHARSET_INFO *db_cl;
const char *db_name = thd->strmake(name->m_db.str, name->m_db.length);
const char *sr_name = thd->strmake(name->m_name.str, name->m_name.length);
bool invalid_creation_ctx = false;
if (load_charset(thd->mem_root,
proc_tbl->field[MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT],
thd->variables.character_set_client, &client_cs)) {
LogErr(WARNING_LEVEL, ER_SR_BOGUS_VALUE, db_name, sr_name,
"mysql.proc.character_set_client");
invalid_creation_ctx = true;
}
if (load_collation(thd->mem_root,
proc_tbl->field[MYSQL_PROC_FIELD_COLLATION_CONNECTION],
thd->variables.collation_connection, &connection_cl)) {
LogErr(WARNING_LEVEL, ER_SR_BOGUS_VALUE, db_name, sr_name,
"mysql.proc.collation_connection.");
invalid_creation_ctx = true;
}
if (load_collation(thd->mem_root,
proc_tbl->field[MYSQL_PROC_FIELD_DB_COLLATION], nullptr,
&db_cl)) {
LogErr(WARNING_LEVEL, ER_SR_BOGUS_VALUE, db_name, sr_name,
"mysql.proc.db_collation.");
invalid_creation_ctx = true;
}
if (invalid_creation_ctx) {
LogErr(WARNING_LEVEL, ER_SR_INVALID_CONTEXT, db_name, sr_name);
}
/*
If we failed to retrieve the database collation, load the default one
from the disk.
*/
if (!db_cl) get_default_db_collation(thd, name->m_db.str, &db_cl);
/* Create the context. */
return new (thd->mem_root)
Stored_routine_creation_ctx(client_cs, connection_cl, db_cl);
}
Stored_program_creation_ctx *Stored_routine_creation_ctx::clone(
MEM_ROOT *mem_root) {
return new (mem_root)
Stored_routine_creation_ctx(m_client_cs, m_connection_cl, m_db_cl);
}
Object_creation_ctx *Stored_routine_creation_ctx::create_backup_ctx(
THD *thd) const {
DBUG_TRACE;
return new (thd->mem_root) Stored_routine_creation_ctx(thd);
}
void Stored_routine_creation_ctx::delete_backup_ctx() { ::destroy_at(this); }
/**
Acquire Shared MDL lock on the routine object.
@param thd Thread context
@param type Type of the routine (enum_sp_type::PROCEDURE/...)
@param name Name of the routine
@param mdl_lock_type Type of MDL lock be acquired on the routine.
@retval false Success
@retval true Error
*/
static bool lock_routine_name(THD *thd, enum_sp_type type, const sp_name *name,
enum_mdl_type mdl_lock_type) {
DBUG_TRACE;
assert(mdl_lock_type == MDL_SHARED_HIGH_PRIO || mdl_lock_type == MDL_SHARED);
MDL_key mdl_key;
if (type == enum_sp_type::FUNCTION)
dd::Function::create_mdl_key(name->m_db.str, name->m_name.str, &mdl_key);
else if (type == enum_sp_type::PROCEDURE)
dd::Procedure::create_mdl_key(name->m_db.str, name->m_name.str, &mdl_key);
else if (type == enum_sp_type::LIBRARY)
dd::Library::create_mdl_key(name->m_db.str, name->m_name.str, &mdl_key);
else
assert(false);
// MDL Lock request on the routine.
MDL_request routine_request;
MDL_REQUEST_INIT_BY_KEY(&routine_request, &mdl_key, mdl_lock_type,
MDL_TRANSACTION);
// Acquire MDL locks
if (thd->mdl_context.acquire_lock(&routine_request,
thd->variables.lock_wait_timeout))
return true;
return false;
}
/**
Return appropriate error about recursion limit reaching
@param thd Thread handle
@param sp The stored procedure executed
@remark For functions, triggers and libraries we return error about
prohibited recursion. For stored procedures we
return about reaching recursion limit.
*/
static void recursion_level_error(THD *thd, sp_head *sp) {
if (sp->m_type == enum_sp_type::PROCEDURE) {
my_error(ER_SP_RECURSION_LIMIT, MYF(0),
static_cast<int>(thd->variables.max_sp_recursion_depth),
sp->m_name.str);
} else {
// The library is not executable and does not support recursion.
assert(sp->m_type != enum_sp_type::LIBRARY);
my_error(ER_SP_NO_RECURSION, MYF(0));
}
}
/**
Find routine definition in data dictionary table and create corresponding
sp_head object for it.
@param thd Thread context
@param type Type of routine (PROCEDURE/...)
@param name Name of routine
@param sphp Out parameter in which pointer to created sp_head
object is returned (0 in case of error).
@note
This function may damage current LEX during execution, so it is good
idea to create temporary LEX and make it active before calling it.
@retval
SP_OK Success
@retval
non-SP_OK Error (one of special codes like SP_DOES_NOT_EXISTS)
*/
static enum_sp_return_code db_find_routine(THD *thd, enum_sp_type type,
const sp_name *name,
sp_head **sphp) {
DBUG_TRACE;
DBUG_PRINT("enter",
("type: %d name: %.*s", static_cast<int>(type),
static_cast<int>(name->m_name.length), name->m_name.str));
*sphp = nullptr; // In case of errors
// Grab shared MDL lock on routine object.
if (lock_routine_name(thd, type, name, MDL_SHARED)) return SP_INTERNAL_ERROR;
// Find routine in the data dictionary.
enum_sp_return_code ret;
const dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
const dd::Routine *routine = nullptr;
bool error = true;
if (type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire<dd::Function>(name->m_db.str,
name->m_name.str, &routine);
else if (type == enum_sp_type::PROCEDURE)
error = thd->dd_client()->acquire<dd::Procedure>(
name->m_db.str, name->m_name.str, &routine);
else
assert(false);
if (error) return SP_INTERNAL_ERROR;
if (routine == nullptr) return SP_DOES_NOT_EXISTS;
// prepare sp_chistics from the dd::routine object.
st_sp_chistics sp_chistics;
prepare_sp_chistics_from_dd_routine(thd, routine, &sp_chistics);
// prepare stored routine's return type string.
dd::String_type return_type_str;
prepare_return_type_string_from_dd_routine(thd, routine, &return_type_str);
// prepare stored routine's parameters string.
dd::String_type params_str;
prepare_params_string_from_dd_routine(thd, routine, ¶ms_str);
// Create stored routine creation context from the dd::Routine object.
Stored_program_creation_ctx *creation_ctx =
Stored_routine_creation_ctx::create_routine_creation_ctx(routine);
if (creation_ctx == nullptr) return SP_INTERNAL_ERROR;
DBUG_EXECUTE_IF("fail_stored_routine_load", return SP_PARSE_ERROR;);
/*
Create sp_head object for the stored routine from the information obtained
from the dd::Routine object.
*/
ret = db_load_routine(
thd, type, name->m_db.str, name->m_db.length, routine->name().c_str(),
routine->name().length(), sphp, routine->sql_mode(), params_str.c_str(),
return_type_str.c_str(), routine->definition().c_str(), &sp_chistics,
routine->definer_user().c_str(), routine->definer_host().c_str(),
routine->created(true), routine->last_altered(true), creation_ctx);
return ret;
}
namespace {
/**
Silence DEPRECATED SYNTAX warnings when loading a stored procedure
into the cache.
*/
class Silence_deprecated_warning final : public Internal_error_handler {
public:
bool handle_condition(THD *, uint sql_errno, const char *,
Sql_condition::enum_severity_level *level,
const char *) override {
if ((sql_errno == ER_WARN_DEPRECATED_SYNTAX ||
sql_errno == ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT) &&
(*level) == Sql_condition::SL_WARNING)
return true;
return false;
}
};
} // namespace
/**
The function parses input strings and returns SP structure.
@param[in] thd Thread handler
@param[in] defstr CREATE... string
@param[in] sql_mode SQL mode
@param[in] creation_ctx Creation context of stored routines
@retval Pointer on sp_head struct Success
@retval NULL error
*/
static sp_head *sp_compile(THD *thd, String *defstr, sql_mode_t sql_mode,
Stored_program_creation_ctx *creation_ctx) {
sp_head *sp;
const sql_mode_t old_sql_mode = thd->variables.sql_mode;
const ha_rows old_select_limit = thd->variables.select_limit;
sp_rcontext *sp_runtime_ctx_saved = thd->sp_runtime_ctx;
Silence_deprecated_warning warning_handler;
Parser_state parser_state;
sql_digest_state *parent_digest = thd->m_digest;
PSI_statement_locker *parent_locker = thd->m_statement_psi;
thd->variables.sql_mode = sql_mode;
thd->variables.select_limit = HA_POS_ERROR;
if (parser_state.init(thd, defstr->c_ptr(), defstr->length())) {
thd->variables.sql_mode = old_sql_mode;
thd->variables.select_limit = old_select_limit;
return nullptr;
}
lex_start(thd);
thd->push_internal_handler(&warning_handler);
thd->sp_runtime_ctx = nullptr;
thd->m_digest = nullptr;
thd->m_statement_psi = nullptr;
if (parse_sql(thd, &parser_state, creation_ctx) || thd->lex == nullptr) {
sp = thd->lex->sphead;
sp_head::destroy(sp);
sp = nullptr;
} else {
sp = thd->lex->sphead;
}
thd->m_digest = parent_digest;
thd->m_statement_psi = parent_locker;
thd->pop_internal_handler();
thd->sp_runtime_ctx = sp_runtime_ctx_saved;
thd->variables.sql_mode = old_sql_mode;
thd->variables.select_limit = old_select_limit;
#ifdef HAVE_PSI_SP_INTERFACE
if (sp != nullptr)
sp->m_sp_share =
MYSQL_GET_SP_SHARE(static_cast<uint>(sp->m_type), sp->m_db.str,
sp->m_db.length, sp->m_name.str, sp->m_name.length);
#endif
return sp;
}
class Bad_db_error_handler : public Internal_error_handler {
public:
Bad_db_error_handler() : m_error_caught(false) {}
bool handle_condition(THD *, uint sql_errno, const char *,
Sql_condition::enum_severity_level *,
const char *) override {
if (sql_errno == ER_BAD_DB_ERROR) {
m_error_caught = true;
return true;
}
return false;
}
bool error_caught() const { return m_error_caught; }
private:
bool m_error_caught;
};
enum_sp_return_code db_load_routine(
THD *thd, enum_sp_type type, const char *sp_db, size_t sp_db_len,
const char *sp_name, size_t sp_name_len, sp_head **sphp,
sql_mode_t sql_mode, const char *params, const char *returns,
const char *body, st_sp_chistics *sp_chistics, const char *definer_user,
const char *definer_host, longlong created, longlong modified,
Stored_program_creation_ctx *creation_ctx) {
LEX *old_lex = thd->lex, newlex;
char saved_cur_db_name_buf[NAME_LEN + 1];
LEX_STRING saved_cur_db_name = {saved_cur_db_name_buf,
sizeof(saved_cur_db_name_buf)};
bool cur_db_changed;
Bad_db_error_handler db_not_exists_handler;
enum_sp_return_code ret = SP_OK;
thd->lex = &newlex;
newlex.thd = thd;
newlex.set_current_query_block(nullptr);
String defstr;
defstr.set_charset(creation_ctx->get_client_cs());
const LEX_CSTRING user = {definer_user, strlen(definer_user)};
const LEX_CSTRING host = {definer_host, strlen(definer_host)};
if (create_string(thd, &defstr, type, nullptr, 0, sp_name, sp_name_len,
params, strlen(params), returns, strlen(returns), body,
strlen(body), sp_chistics, user, host, sql_mode, false)) {
ret = SP_INTERNAL_ERROR;
goto end;
}
thd->push_internal_handler(&db_not_exists_handler);
/*
Change the current database (if needed).
TODO: why do we force switch here?
*/
if (mysql_opt_change_db(thd, {sp_db, sp_db_len}, &saved_cur_db_name, true,
&cur_db_changed)) {
ret = SP_INTERNAL_ERROR;
thd->pop_internal_handler();
goto end;
}
thd->pop_internal_handler();
if (db_not_exists_handler.error_caught()) {
ret = SP_NO_DB_ERROR;
my_error(ER_BAD_DB_ERROR, MYF(0), sp_db);
goto end;
}
{
*sphp = sp_compile(thd, &defstr, sql_mode, creation_ctx);
/*
Force switching back to the saved current database (if changed),
because it may be NULL. In this case, mysql_change_db() would
generate an error.
*/
if (cur_db_changed &&
mysql_change_db(thd, to_lex_cstring(saved_cur_db_name), true)) {
sp_head::destroy(*sphp);
*sphp = nullptr;
ret = SP_INTERNAL_ERROR;
goto end;
}
if (!*sphp) {
ret = SP_PARSE_ERROR;
goto end;
}
(*sphp)->set_definer(user, host);
(*sphp)->set_info(created, modified, sp_chistics, sql_mode);
(*sphp)->set_creation_ctx(creation_ctx);
(*sphp)->optimize();
/*
Not strictly necessary to invoke this method here, since we know
that we've parsed CREATE PROCEDURE/FUNCTION and not an
UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
maintain the invariant that this method is called for each
distinct statement, in case its logic is extended with other
types of analyses in future.
*/
newlex.set_trg_event_type_for_tables();
}
end:
thd->lex->sphead = nullptr;
lex_end(thd->lex);
thd->lex = old_lex;
return ret;
}
/**
Method to check if routine with same name already exists.
@param thd Thread context.
@param sp Stored routine object to store.
@param if_not_exists True if 'IF NOT EXISTS' clause was specified.
@param[out] already_exists Set to true if routine already exists.
@retval false Success.
@retval true Error.
*/
static bool check_routine_already_exists(THD *thd, sp_head *sp,
bool if_not_exists,
bool &already_exists) {
assert(!already_exists);
const dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
bool error = true;
const dd::Routine *sr = nullptr;
if (sp->m_type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire<dd::Function>(sp->m_db.str,
sp->m_name.str, &sr);
else if (sp->m_type == enum_sp_type::PROCEDURE)
error = thd->dd_client()->acquire<dd::Procedure>(sp->m_db.str,
sp->m_name.str, &sr);
else if (sp->m_type == enum_sp_type::LIBRARY)
error = thd->dd_client()->acquire<dd::Library>(sp->m_db.str, sp->m_name.str,
&sr);
else
assert(false);
if (error) {
// Error is reported by DD API framework.
return true;
}
if (sr == nullptr) {
// Routine with same name does not exist.
return false;
}
already_exists = true;
if (if_not_exists) {
push_warning_printf(thd, Sql_condition::SL_NOTE, ER_SP_ALREADY_EXISTS,
ER_THD(thd, ER_SP_ALREADY_EXISTS),
SP_TYPE_STRING(sp->m_type), sp->m_name.str);
return false;
}
my_error(ER_SP_ALREADY_EXISTS, MYF(0), SP_TYPE_STRING(sp->m_type),
sp->m_name.str);
return true;
}
/**
Precheck for create routine statement.
@param thd Thread context.
@param sp Stored routine object to store.
@retval false Success.
@retval true Error.
*/
static bool create_routine_precheck(THD *thd, sp_head *sp) {
/*
Check if stored function creation is allowed only to the users having SUPER
privileges.
*/
if (mysql_bin_log.is_open() && (sp->m_type == enum_sp_type::FUNCTION) &&
!trust_function_creators) {
if (!sp->m_chistics->detistic) {
/*
Note that this test is not perfect; one could use
a non-deterministic read-only function in an update statement.
*/
const enum enum_sp_data_access access =
(sp->m_chistics->daccess == SP_DEFAULT_ACCESS)
? static_cast<enum_sp_data_access>(SP_DEFAULT_ACCESS_MAPPING)
: sp->m_chistics->daccess;
if (access == SP_CONTAINS_SQL || access == SP_MODIFIES_SQL_DATA) {
my_error(ER_BINLOG_UNSAFE_ROUTINE, MYF(0));
return true;
}
}
if (!(thd->security_context()->check_access(SUPER_ACL))) {
my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0));
return true;
}
}
/*
Check routine body length.
Note: Length of routine name and parameters name is already verified in
parsing phase.
*/
if (sp->m_body.length > MYSQL_STORED_ROUTINE_BODY_LENGTH ||
DBUG_EVALUATE_IF("simulate_routine_length_error", 1, 0)) {
my_error(ER_TOO_LONG_BODY, MYF(0), sp->m_name.str);
return true;
}
// Validate body definition to avoid invalid UTF8 characters.
std::string invalid_sub_str;
if (is_invalid_string(sp->m_body_utf8, system_charset_info,
invalid_sub_str)) {
// Provide contextual information
my_error(ER_DEFINITION_CONTAINS_INVALID_STRING, MYF(0), "stored routine",
sp->m_db.str, sp->m_name.str, system_charset_info->csname,
invalid_sub_str.c_str());
return true;
}
// Validate routine comment.
if (sp->m_chistics->comment.length) {
// validate comment string to avoid invalid utf8 characters.
if (is_invalid_string(LEX_CSTRING{sp->m_chistics->comment.str,
sp->m_chistics->comment.length},
system_charset_info, invalid_sub_str)) {
// Provide contextual information
my_error(ER_COMMENT_CONTAINS_INVALID_STRING, MYF(0), "stored routine",
(std::string(sp->m_db.str) + "." + std::string(sp->m_name.str))
.c_str(),
system_charset_info->csname, invalid_sub_str.c_str());
return true;
}
// Check comment string length.
if (check_string_char_length(
{sp->m_chistics->comment.str, sp->m_chistics->comment.length}, "",
MYSQL_STORED_ROUTINE_COMMENT_LENGTH, system_charset_info, true)) {
my_error(ER_TOO_LONG_ROUTINE_COMMENT, MYF(0), sp->m_chistics->comment.str,
MYSQL_STORED_ROUTINE_COMMENT_LENGTH);
return true;
}
}
return false;
}
/**
Method to log create routine event to binlog.
@param thd Thread context.
@param sp Stored routine object to store.
@param definer Definer of the routine.
@param if_not_exists True if 'IF NOT EXISTS' clause was specified.
@param already_exists True if routine already exists.
@retval false success
@retval true error
*/
static bool sp_binlog_create_routine_stmt(THD *thd, sp_head *sp,
const LEX_USER *definer,
bool if_not_exists,
bool already_exists) {
String log_query;
log_query.set_charset(system_charset_info);
String retstr(64);
retstr.set_charset(system_charset_info);
if (sp->m_type == enum_sp_type::FUNCTION) sp->returns_type(thd, &retstr);
if (create_string(thd, &log_query, sp->m_type,
(sp->m_explicit_name ? sp->m_db.str : nullptr),
(sp->m_explicit_name ? sp->m_db.length : 0), sp->m_name.str,
sp->m_name.length, sp->m_params.str, sp->m_params.length,
retstr.c_ptr(), retstr.length(), sp->m_body.str,
sp->m_body.length, sp->m_chistics, definer->user,
definer->host, thd->variables.sql_mode, if_not_exists))
return true;
thd->add_to_binlog_accessed_dbs(sp->m_db.str);
/*
This statement will be replicated as a statement, even when using
row-based replication.
*/
const Save_and_Restore_binlog_format_state binlog_format_state(thd);
if (write_bin_log(thd, true, log_query.c_ptr(), log_query.length(),
!already_exists))
return true;
return false;
}
/*
Private helper function to translate between the enum_sp_type and MDL_key.
*/
static MDL_key::enum_mdl_namespace get_mdl_type(enum_sp_type sp_type) {
if (sp_type == enum_sp_type::FUNCTION) return MDL_key::FUNCTION;
if (sp_type == enum_sp_type::PROCEDURE) return MDL_key::PROCEDURE;
if (sp_type == enum_sp_type::LIBRARY) return MDL_key::LIBRARY;
assert(false);
return {};
}
/**
Creates a stored routine.
Atomicity:
The operation to create a stored routine 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, stored routines 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 context.
@param sp Stored routine object to store.
@param definer Definer of the SP.
@param if_not_exists True if 'IF NOT EXISTS' clause was specified.
@param[out] sp_already_exists Set to true if routine already exists.
@retval false Success.
@retval true Error.
*/
bool sp_create_routine(THD *thd, sp_head *sp, const LEX_USER *definer,
bool if_not_exists, bool &sp_already_exists) {
DBUG_TRACE;
DBUG_PRINT("enter", ("type: %d name: %.*s", static_cast<int>(sp->m_type),
static_cast<int>(sp->m_name.length), sp->m_name.str));
assert(sp->m_type == enum_sp_type::PROCEDURE ||
sp->m_type == enum_sp_type::FUNCTION ||
sp->m_type == enum_sp_type::LIBRARY);
assert(!sp_already_exists);
/* Grab an exclusive MDL lock. */
const MDL_key::enum_mdl_namespace mdl_type = get_mdl_type(sp->m_type);
if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str)) {
my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(sp->m_type),
sp->m_name.str);
return true;
}
DEBUG_SYNC(thd, "after_acquiring_mdl_lock_on_routine");
const dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
const dd::Schema *schema = nullptr;
// Check whether routine with same name already exists.
if (check_routine_already_exists(thd, sp, if_not_exists, sp_already_exists)) {
/* If this happens, an error should have been reported. */
return true;
}
if (sp_already_exists) {
assert(if_not_exists);
/*
Routine with same name exists, warning is already reported. Log
create routine event to binlog.
*/
if (mysql_bin_log.is_open() &&
sp_binlog_create_routine_stmt(thd, sp, definer, if_not_exists,
sp_already_exists))
goto err_with_rollback;
return false;
}
if (create_routine_precheck(thd, sp)) {
/* If this happens, an error should have been reported. */
return true;
}
DBUG_EXECUTE_IF("fail_while_acquiring_routine_schema_obj",
DBUG_SET("+d,fail_while_acquiring_dd_object"););
// Check that a database with this name exists.
if (thd->dd_client()->acquire(sp->m_db.str, &schema)) {
DBUG_EXECUTE_IF("fail_while_acquiring_routine_schema_obj",
DBUG_SET("-d,fail_while_acquiring_dd_object"););
// Error is reported by DD API framework.
return true;
}
if (schema == nullptr) {
my_error(ER_BAD_DB_ERROR, MYF(0), sp->m_db.str);
return true;
}
// Create a stored routine.
if (dd::create_routine(thd, *schema, sp, definer))
goto err_report_with_rollback;
// Update referencing views metadata.
{
const sp_name spname({sp->m_db.str, sp->m_db.length}, sp->m_name, false);
if (sp->m_type == enum_sp_type::FUNCTION &&
update_referencing_views_metadata(thd, &spname)) {
/* If this happens, an error should have been reported. */
goto err_with_rollback;
}
}
// Log stored routine create event.
if (mysql_bin_log.is_open() &&
sp_binlog_create_routine_stmt(thd, sp, definer, if_not_exists, false))
goto err_report_with_rollback;
// Commit changes to the data-dictionary and binary log.
if (DBUG_EVALUATE_IF("simulate_create_routine_failure", true, false) ||
trans_commit_stmt(thd) || trans_commit(thd))
goto err_report_with_rollback;
sp_cache_invalidate();
return false;
err_report_with_rollback:
my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(sp->m_type),
sp->m_name.str);
err_with_rollback:
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);
return true;
}
/**
Drops a stored routine.
Atomicity:
The operation to drop a stored routine 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, stored routines 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 context.
@param type Stored routine type
(PROCEDURE or FUNCTION)
@param name Stored routine name.
@return Error code. SP_OK is returned on success. Other SP_ constants are
used to indicate about errors.
*/
enum_sp_return_code sp_drop_routine(THD *thd, enum_sp_type type,
sp_name *name) {
DBUG_TRACE;
DBUG_PRINT("enter",
("type: %d name: %.*s", static_cast<int>(type),
static_cast<int>(name->m_name.length), name->m_name.str));
assert(type == enum_sp_type::PROCEDURE || type == enum_sp_type::FUNCTION ||
type == enum_sp_type::LIBRARY);
/* Grab an exclusive MDL lock. */
MDL_key::enum_mdl_namespace mdl_type = get_mdl_type(type);
if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str))
return SP_DROP_FAILED;
DEBUG_SYNC(thd, "after_acquiring_mdl_lock_on_routine");
const dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
const dd::Routine *routine = nullptr;
bool error = true;
if (type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire<dd::Function>(name->m_db.str,
name->m_name.str, &routine);
else if (type == enum_sp_type::PROCEDURE)
error = thd->dd_client()->acquire<dd::Procedure>(
name->m_db.str, name->m_name.str, &routine);
else if (type == enum_sp_type::LIBRARY)