1 | /* |
2 | Copyright (c) 2005, 2013, Oracle and/or its affiliates. |
3 | Copyright (c) 2017, MariaDB Corporation. |
4 | |
5 | This program is free software; you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published by |
7 | the Free Software Foundation; version 2 of the License. |
8 | |
9 | This program is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | GNU General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU General Public License |
15 | along with this program; if not, write to the Free Software |
16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
17 | |
18 | #include "mariadb.h" |
19 | #include "sql_priv.h" |
20 | #include "unireg.h" |
21 | #include "sql_parse.h" // check_access |
22 | #include "sql_base.h" // close_mysql_tables |
23 | #include "sql_show.h" // append_definer |
24 | #include "events.h" |
25 | #include "sql_db.h" // check_db_dir_existence |
26 | #include "sql_table.h" // write_bin_log |
27 | #include "tztime.h" // struct Time_zone |
28 | #include "sql_acl.h" // EVENT_ACL |
29 | #include "records.h" // init_read_record, end_read_record |
30 | #include "event_data_objects.h" |
31 | #include "event_db_repository.h" |
32 | #include "event_queue.h" |
33 | #include "event_scheduler.h" |
34 | #include "sp_head.h" // for Stored_program_creation_ctx |
35 | #include "set_var.h" |
36 | #include "lock.h" // lock_object_name |
37 | |
38 | /** |
39 | @addtogroup Event_Scheduler |
40 | @{ |
41 | */ |
42 | |
43 | /* |
44 | TODO list : |
45 | - CREATE EVENT should not go into binary log! Does it now? The SQL statements |
46 | issued by the EVENT are replicated. |
47 | I have an idea how to solve the problem at failover. So the status field |
48 | will be ENUM('DISABLED', 'ENABLED', 'SLAVESIDE_DISABLED'). |
49 | In this case when CREATE EVENT is replicated it should go into the binary |
50 | as SLAVESIDE_DISABLED if it is ENABLED, when it's created as DISABLEd it |
51 | should be replicated as disabled. If an event is ALTERed as DISABLED the |
52 | query should go untouched into the binary log, when ALTERed as enable then |
53 | it should go as SLAVESIDE_DISABLED. This is regarding the SQL interface. |
54 | TT routines however modify mysql.event internally and this does not go the |
55 | log so in this case queries has to be injected into the log...somehow... or |
56 | maybe a solution is RBR for this case, because the event may go only from |
57 | ENABLED to DISABLED status change and this is safe for replicating. As well |
58 | an event may be deleted which is also safe for RBR. |
59 | |
60 | - Add logging to file |
61 | |
62 | */ |
63 | |
64 | |
65 | /* |
66 | If the user (un)intentionally removes an event directly from mysql.event |
67 | the following sequence has to be used to be able to remove the in-memory |
68 | counterpart. |
69 | 1. CREATE EVENT the_name ON SCHEDULE EVERY 1 SECOND DISABLE DO SELECT 1; |
70 | 2. DROP EVENT the_name |
71 | |
72 | In other words, the first one will create a row in mysql.event . In the |
73 | second step because there will be a line, disk based drop will pass and |
74 | the scheduler will remove the memory counterpart. The reason is that |
75 | in-memory queue does not check whether the event we try to drop from memory |
76 | is disabled. Disabled events are not kept in-memory because they are not |
77 | eligible for execution. |
78 | */ |
79 | |
80 | Event_queue *Events::event_queue; |
81 | Event_scheduler *Events::scheduler; |
82 | Event_db_repository *Events::db_repository; |
83 | ulong Events::opt_event_scheduler= Events::EVENTS_OFF; |
84 | ulong Events::startup_state= Events::EVENTS_OFF; |
85 | ulong Events::inited; |
86 | |
87 | |
88 | /* |
89 | Compares 2 LEX strings regarding case. |
90 | |
91 | SYNOPSIS |
92 | sortcmp_lex_string() |
93 | s First LEX_STRING |
94 | t Second LEX_STRING |
95 | cs Charset |
96 | |
97 | RETURN VALUE |
98 | -1 s < t |
99 | 0 s == t |
100 | 1 s > t |
101 | */ |
102 | |
103 | int sortcmp_lex_string(const LEX_CSTRING *s, const LEX_CSTRING *t, |
104 | const CHARSET_INFO *cs) |
105 | { |
106 | return cs->coll->strnncollsp(cs, (uchar *) s->str, s->length, |
107 | (uchar *) t->str, t->length); |
108 | } |
109 | |
110 | |
111 | /** |
112 | Push an error into the error stack if the system tables are |
113 | not up to date. |
114 | */ |
115 | |
116 | bool Events::check_if_system_tables_error() |
117 | { |
118 | DBUG_ENTER("Events::check_if_system_tables_error" ); |
119 | |
120 | if (!inited) |
121 | { |
122 | my_error(ER_EVENTS_DB_ERROR, MYF(0)); |
123 | DBUG_RETURN(TRUE); |
124 | } |
125 | |
126 | DBUG_RETURN(FALSE); |
127 | } |
128 | |
129 | |
130 | /** |
131 | Reconstructs interval expression from interval type and expression |
132 | value that is in form of a value of the smalles entity: |
133 | For |
134 | YEAR_MONTH - expression is in months |
135 | DAY_MINUTE - expression is in minutes |
136 | |
137 | SYNOPSIS |
138 | Events::reconstruct_interval_expression() |
139 | buf Preallocated String buffer to add the value to |
140 | interval The interval type (for instance YEAR_MONTH) |
141 | expression The value in the lowest entity |
142 | |
143 | RETURN VALUE |
144 | 0 OK |
145 | 1 Error |
146 | */ |
147 | |
148 | int |
149 | Events::reconstruct_interval_expression(String *buf, interval_type interval, |
150 | longlong expression) |
151 | { |
152 | ulonglong expr= expression; |
153 | char tmp_buff[128], *end; |
154 | bool close_quote= TRUE; |
155 | int multipl= 0; |
156 | char separator=':'; |
157 | |
158 | switch (interval) { |
159 | case INTERVAL_YEAR_MONTH: |
160 | multipl= 12; |
161 | separator= '-'; |
162 | goto common_1_lev_code; |
163 | case INTERVAL_DAY_HOUR: |
164 | multipl= 24; |
165 | separator= ' '; |
166 | goto common_1_lev_code; |
167 | case INTERVAL_HOUR_MINUTE: |
168 | case INTERVAL_MINUTE_SECOND: |
169 | multipl= 60; |
170 | common_1_lev_code: |
171 | buf->append('\''); |
172 | end= longlong10_to_str(expression/multipl, tmp_buff, 10); |
173 | buf->append(tmp_buff, (uint) (end- tmp_buff)); |
174 | expr= expr - (expr/multipl)*multipl; |
175 | break; |
176 | case INTERVAL_DAY_MINUTE: |
177 | { |
178 | ulonglong tmp_expr= expr; |
179 | |
180 | tmp_expr/=(24*60); |
181 | buf->append('\''); |
182 | end= longlong10_to_str(tmp_expr, tmp_buff, 10); |
183 | buf->append(tmp_buff, (uint) (end- tmp_buff));// days |
184 | buf->append(' '); |
185 | |
186 | tmp_expr= expr - tmp_expr*(24*60);//minutes left |
187 | end= longlong10_to_str(tmp_expr/60, tmp_buff, 10); |
188 | buf->append(tmp_buff, (uint) (end- tmp_buff));// hours |
189 | |
190 | expr= tmp_expr - (tmp_expr/60)*60; |
191 | /* the code after the switch will finish */ |
192 | break; |
193 | } |
194 | case INTERVAL_HOUR_SECOND: |
195 | { |
196 | ulonglong tmp_expr= expr; |
197 | |
198 | buf->append('\''); |
199 | end= longlong10_to_str(tmp_expr/3600, tmp_buff, 10); |
200 | buf->append(tmp_buff, (uint) (end- tmp_buff));// hours |
201 | buf->append(':'); |
202 | |
203 | tmp_expr= tmp_expr - (tmp_expr/3600)*3600; |
204 | end= longlong10_to_str(tmp_expr/60, tmp_buff, 10); |
205 | buf->append(tmp_buff, (uint) (end- tmp_buff));// minutes |
206 | |
207 | expr= tmp_expr - (tmp_expr/60)*60; |
208 | /* the code after the switch will finish */ |
209 | break; |
210 | } |
211 | case INTERVAL_DAY_SECOND: |
212 | { |
213 | ulonglong tmp_expr= expr; |
214 | |
215 | tmp_expr/=(24*3600); |
216 | buf->append('\''); |
217 | end= longlong10_to_str(tmp_expr, tmp_buff, 10); |
218 | buf->append(tmp_buff, (uint) (end- tmp_buff));// days |
219 | buf->append(' '); |
220 | |
221 | tmp_expr= expr - tmp_expr*(24*3600);//seconds left |
222 | end= longlong10_to_str(tmp_expr/3600, tmp_buff, 10); |
223 | buf->append(tmp_buff, (uint) (end- tmp_buff));// hours |
224 | buf->append(':'); |
225 | |
226 | tmp_expr= tmp_expr - (tmp_expr/3600)*3600; |
227 | end= longlong10_to_str(tmp_expr/60, tmp_buff, 10); |
228 | buf->append(tmp_buff, (uint) (end- tmp_buff));// minutes |
229 | |
230 | expr= tmp_expr - (tmp_expr/60)*60; |
231 | /* the code after the switch will finish */ |
232 | break; |
233 | } |
234 | case INTERVAL_DAY_MICROSECOND: |
235 | case INTERVAL_HOUR_MICROSECOND: |
236 | case INTERVAL_MINUTE_MICROSECOND: |
237 | case INTERVAL_SECOND_MICROSECOND: |
238 | case INTERVAL_MICROSECOND: |
239 | my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND" ); |
240 | return 1; |
241 | case INTERVAL_QUARTER: |
242 | expr/= 3; |
243 | close_quote= FALSE; |
244 | break; |
245 | case INTERVAL_WEEK: |
246 | expr/= 7; |
247 | close_quote= FALSE; |
248 | break; |
249 | default: |
250 | close_quote= FALSE; |
251 | break; |
252 | } |
253 | if (close_quote) |
254 | buf->append(separator); |
255 | end= longlong10_to_str(expr, tmp_buff, 10); |
256 | buf->append(tmp_buff, (uint) (end- tmp_buff)); |
257 | if (close_quote) |
258 | buf->append('\''); |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | |
264 | /** |
265 | Create a new query string for removing executable comments |
266 | for avoiding leak and keeping consistency of the execution |
267 | on master and slave. |
268 | |
269 | @param[in] thd Thread handler |
270 | @param[in] buf Query string |
271 | |
272 | @return |
273 | 0 ok |
274 | 1 error |
275 | */ |
276 | static int |
277 | create_query_string(THD *thd, String *buf) |
278 | { |
279 | buf->length(0); |
280 | /* Append the "CREATE" part of the query */ |
281 | if (thd->lex->create_info.or_replace()) |
282 | { |
283 | if (buf->append(STRING_WITH_LEN("CREATE OR REPLACE " ))) |
284 | return 1; |
285 | } |
286 | else if (buf->append(STRING_WITH_LEN("CREATE " ))) |
287 | return 1; |
288 | /* Append definer */ |
289 | append_definer(thd, buf, &(thd->lex->definer->user), &(thd->lex->definer->host)); |
290 | /* Append the left part of thd->query after "DEFINER" part */ |
291 | if (buf->append(thd->lex->stmt_definition_begin, |
292 | thd->lex->stmt_definition_end - |
293 | thd->lex->stmt_definition_begin)) |
294 | return 1; |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | |
300 | /** |
301 | Create a new event. |
302 | |
303 | @param[in,out] thd THD |
304 | @param[in] parse_data Event's data from parsing stage |
305 | |
306 | In case there is an event with the same name (db) and |
307 | IF NOT EXISTS is specified, an warning is put into the stack. |
308 | @sa Events::drop_event for the notes about locking, pre-locking |
309 | and Events DDL. |
310 | |
311 | @retval FALSE OK |
312 | @retval TRUE Error (reported) |
313 | */ |
314 | |
315 | bool |
316 | Events::create_event(THD *thd, Event_parse_data *parse_data) |
317 | { |
318 | bool ret; |
319 | bool event_already_exists; |
320 | enum_binlog_format save_binlog_format; |
321 | DBUG_ENTER("Events::create_event" ); |
322 | |
323 | if (unlikely(check_if_system_tables_error())) |
324 | DBUG_RETURN(TRUE); |
325 | |
326 | /* |
327 | Perform semantic checks outside of Event_db_repository: |
328 | once CREATE EVENT is supported in prepared statements, the |
329 | checks will be moved to PREPARE phase. |
330 | */ |
331 | if (parse_data->check_parse_data(thd)) |
332 | DBUG_RETURN(TRUE); |
333 | |
334 | /* At create, one of them must be set */ |
335 | DBUG_ASSERT(parse_data->expression || parse_data->execute_at); |
336 | |
337 | if (check_access(thd, EVENT_ACL, parse_data->dbname.str, NULL, NULL, 0, 0)) |
338 | DBUG_RETURN(TRUE); |
339 | WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) |
340 | |
341 | if (lock_object_name(thd, MDL_key::EVENT, |
342 | parse_data->dbname.str, parse_data->name.str)) |
343 | DBUG_RETURN(TRUE); |
344 | |
345 | if (check_db_dir_existence(parse_data->dbname.str)) |
346 | { |
347 | my_error(ER_BAD_DB_ERROR, MYF(0), parse_data->dbname.str); |
348 | DBUG_RETURN(TRUE); |
349 | } |
350 | |
351 | if (parse_data->do_not_create) |
352 | DBUG_RETURN(FALSE); |
353 | /* |
354 | Turn off row binlogging of this statement and use statement-based |
355 | so that all supporting tables are updated for CREATE EVENT command. |
356 | */ |
357 | save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); |
358 | |
359 | if (thd->lex->create_info.or_replace() && event_queue) |
360 | event_queue->drop_event(thd, &parse_data->dbname, &parse_data->name); |
361 | |
362 | /* On error conditions my_error() is called so no need to handle here */ |
363 | if (!(ret= db_repository->create_event(thd, parse_data, |
364 | &event_already_exists))) |
365 | { |
366 | Event_queue_element *new_element; |
367 | bool dropped= 0; |
368 | |
369 | if (!event_already_exists) |
370 | { |
371 | if (!(new_element= new Event_queue_element())) |
372 | ret= TRUE; // OOM |
373 | else if ((ret= db_repository->load_named_event(thd, &parse_data->dbname, |
374 | &parse_data->name, |
375 | new_element))) |
376 | { |
377 | if (!db_repository->drop_event(thd, &parse_data->dbname, |
378 | &parse_data->name, TRUE)) |
379 | dropped= 1; |
380 | delete new_element; |
381 | } |
382 | else |
383 | { |
384 | /* TODO: do not ignore the out parameter and a possible OOM error! */ |
385 | bool created; |
386 | if (event_queue) |
387 | event_queue->create_event(thd, new_element, &created); |
388 | } |
389 | } |
390 | /* |
391 | binlog the create event unless it's been successfully dropped |
392 | */ |
393 | if (!dropped) |
394 | { |
395 | /* Binlog the create event. */ |
396 | DBUG_ASSERT(thd->query() && thd->query_length()); |
397 | char buffer[1024]; |
398 | String log_query(buffer, sizeof(buffer), &my_charset_bin); |
399 | if (create_query_string(thd, &log_query)) |
400 | { |
401 | my_message_sql(ER_STARTUP, |
402 | "Event Error: An error occurred while creating query " |
403 | "string, before writing it into binary log." , |
404 | MYF(ME_NOREFRESH)); |
405 | ret= true; |
406 | } |
407 | else |
408 | { |
409 | /* |
410 | If the definer is not set or set to CURRENT_USER, the value |
411 | of CURRENT_USER will be written into the binary log as the |
412 | definer for the SQL thread. |
413 | */ |
414 | ret= write_bin_log(thd, TRUE, log_query.ptr(), log_query.length()); |
415 | } |
416 | } |
417 | } |
418 | |
419 | thd->restore_stmt_binlog_format(save_binlog_format); |
420 | |
421 | DBUG_RETURN(ret); |
422 | #ifdef WITH_WSREP |
423 | error: |
424 | DBUG_RETURN(TRUE); |
425 | #endif /* WITH_WSREP */ |
426 | } |
427 | |
428 | |
429 | /** |
430 | Alter an event. |
431 | |
432 | @param[in,out] thd THD |
433 | @param[in] parse_data Event's data from parsing stage |
434 | @param[in] new_dbname A new schema name for the event. Set in the case of |
435 | ALTER EVENT RENAME, otherwise is NULL. |
436 | @param[in] new_name A new name for the event. Set in the case of |
437 | ALTER EVENT RENAME |
438 | |
439 | Parameter 'et' contains data about dbname and event name. |
440 | Parameter 'new_name' is the new name of the event, if not null |
441 | this means that RENAME TO was specified in the query |
442 | @sa Events::drop_event for the locking notes. |
443 | |
444 | @retval FALSE OK |
445 | @retval TRUE error (reported) |
446 | */ |
447 | |
448 | bool |
449 | Events::update_event(THD *thd, Event_parse_data *parse_data, |
450 | LEX_CSTRING *new_dbname, LEX_CSTRING *new_name) |
451 | { |
452 | int ret; |
453 | enum_binlog_format save_binlog_format; |
454 | Event_queue_element *new_element; |
455 | |
456 | DBUG_ENTER("Events::update_event" ); |
457 | |
458 | if (unlikely(check_if_system_tables_error())) |
459 | DBUG_RETURN(TRUE); |
460 | |
461 | if (parse_data->check_parse_data(thd) || parse_data->do_not_create) |
462 | DBUG_RETURN(TRUE); |
463 | |
464 | if (check_access(thd, EVENT_ACL, parse_data->dbname.str, NULL, NULL, 0, 0)) |
465 | DBUG_RETURN(TRUE); |
466 | |
467 | WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) |
468 | |
469 | if (lock_object_name(thd, MDL_key::EVENT, |
470 | parse_data->dbname.str, parse_data->name.str)) |
471 | DBUG_RETURN(TRUE); |
472 | |
473 | if (check_db_dir_existence(parse_data->dbname.str)) |
474 | { |
475 | my_error(ER_BAD_DB_ERROR, MYF(0), parse_data->dbname.str); |
476 | DBUG_RETURN(TRUE); |
477 | } |
478 | |
479 | |
480 | if (new_dbname) /* It's a rename */ |
481 | { |
482 | /* Check that the new and the old names differ. */ |
483 | if ( !sortcmp_lex_string(&parse_data->dbname, new_dbname, |
484 | system_charset_info) && |
485 | !sortcmp_lex_string(&parse_data->name, new_name, |
486 | system_charset_info)) |
487 | { |
488 | my_error(ER_EVENT_SAME_NAME, MYF(0)); |
489 | DBUG_RETURN(TRUE); |
490 | } |
491 | |
492 | /* |
493 | And the user has sufficient privileges to use the target database. |
494 | Do it before checking whether the database exists: we don't want |
495 | to tell the user that a database doesn't exist if they can not |
496 | access it. |
497 | */ |
498 | if (check_access(thd, EVENT_ACL, new_dbname->str, NULL, NULL, 0, 0)) |
499 | DBUG_RETURN(TRUE); |
500 | |
501 | /* |
502 | Acquire mdl exclusive lock on target database name. |
503 | */ |
504 | if (lock_object_name(thd, MDL_key::EVENT, |
505 | new_dbname->str, new_name->str)) |
506 | DBUG_RETURN(TRUE); |
507 | |
508 | /* Check that the target database exists */ |
509 | if (check_db_dir_existence(new_dbname->str)) |
510 | { |
511 | my_error(ER_BAD_DB_ERROR, MYF(0), new_dbname->str); |
512 | DBUG_RETURN(TRUE); |
513 | } |
514 | } |
515 | |
516 | /* |
517 | Turn off row binlogging of this statement and use statement-based |
518 | so that all supporting tables are updated for UPDATE EVENT command. |
519 | */ |
520 | save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); |
521 | |
522 | /* On error conditions my_error() is called so no need to handle here */ |
523 | if (!(ret= db_repository->update_event(thd, parse_data, |
524 | new_dbname, new_name))) |
525 | { |
526 | LEX_CSTRING dbname= new_dbname ? *new_dbname : parse_data->dbname; |
527 | LEX_CSTRING name= new_name ? *new_name : parse_data->name; |
528 | |
529 | if (!(new_element= new Event_queue_element())) |
530 | ret= TRUE; // OOM |
531 | else if ((ret= db_repository->load_named_event(thd, &dbname, &name, |
532 | new_element))) |
533 | delete new_element; |
534 | else |
535 | { |
536 | /* |
537 | TODO: check if an update actually has inserted an entry |
538 | into the queue. |
539 | If not, and the element is ON COMPLETION NOT PRESERVE, delete |
540 | it right away. |
541 | */ |
542 | if (event_queue) |
543 | event_queue->update_event(thd, &parse_data->dbname, &parse_data->name, |
544 | new_element); |
545 | /* Binlog the alter event. */ |
546 | DBUG_ASSERT(thd->query() && thd->query_length()); |
547 | ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); |
548 | } |
549 | } |
550 | |
551 | thd->restore_stmt_binlog_format(save_binlog_format); |
552 | DBUG_RETURN(ret); |
553 | #ifdef WITH_WSREP |
554 | error: |
555 | DBUG_RETURN(TRUE); |
556 | #endif /* WITH_WSREP */ |
557 | } |
558 | |
559 | |
560 | /** |
561 | Drops an event |
562 | |
563 | @param[in,out] thd THD |
564 | @param[in] dbname Event's schema |
565 | @param[in] name Event's name |
566 | @param[in] if_exists When this is set and the event does not exist |
567 | a warning is pushed into the warning stack. |
568 | Otherwise the operation produces an error. |
569 | |
570 | @note Similarly to DROP PROCEDURE, we do not allow DROP EVENT |
571 | under LOCK TABLES mode, unless table mysql.event is locked. To |
572 | ensure that, we do not reset & backup the open tables state in |
573 | this function - if in LOCK TABLES or pre-locking mode, this will |
574 | lead to an error 'Table mysql.event is not locked with LOCK |
575 | TABLES' unless it _is_ locked. In pre-locked mode there is |
576 | another barrier - DROP EVENT commits the current transaction, |
577 | and COMMIT/ROLLBACK is not allowed in stored functions and |
578 | triggers. |
579 | |
580 | @retval FALSE OK |
581 | @retval TRUE Error (reported) |
582 | */ |
583 | |
584 | bool |
585 | Events::drop_event(THD *thd, const LEX_CSTRING *dbname, |
586 | const LEX_CSTRING *name, bool if_exists) |
587 | { |
588 | int ret; |
589 | enum_binlog_format save_binlog_format; |
590 | DBUG_ENTER("Events::drop_event" ); |
591 | |
592 | if (unlikely(check_if_system_tables_error())) |
593 | DBUG_RETURN(TRUE); |
594 | |
595 | if (check_access(thd, EVENT_ACL, dbname->str, NULL, NULL, 0, 0)) |
596 | DBUG_RETURN(TRUE); |
597 | |
598 | WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) |
599 | |
600 | /* |
601 | Turn off row binlogging of this statement and use statement-based so |
602 | that all supporting tables are updated for DROP EVENT command. |
603 | */ |
604 | save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); |
605 | |
606 | if (lock_object_name(thd, MDL_key::EVENT, |
607 | dbname->str, name->str)) |
608 | DBUG_RETURN(TRUE); |
609 | /* On error conditions my_error() is called so no need to handle here */ |
610 | if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists))) |
611 | { |
612 | if (event_queue) |
613 | event_queue->drop_event(thd, dbname, name); |
614 | /* Binlog the drop event. */ |
615 | DBUG_ASSERT(thd->query() && thd->query_length()); |
616 | ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); |
617 | } |
618 | |
619 | thd->restore_stmt_binlog_format(save_binlog_format); |
620 | DBUG_RETURN(ret); |
621 | #ifdef WITH_WSREP |
622 | error: |
623 | DBUG_RETURN(TRUE); |
624 | #endif |
625 | } |
626 | |
627 | |
628 | /** |
629 | Drops all events from a schema |
630 | |
631 | @note We allow to drop all events in a schema even if the |
632 | scheduler is disabled. This is to not produce any warnings |
633 | in case of DROP DATABASE and a disabled scheduler. |
634 | |
635 | @param[in,out] thd Thread |
636 | @param[in] db ASCIIZ schema name |
637 | */ |
638 | |
639 | void |
640 | Events::drop_schema_events(THD *thd, const char *db) |
641 | { |
642 | const LEX_CSTRING db_lex= { db, strlen(db) }; |
643 | |
644 | DBUG_ENTER("Events::drop_schema_events" ); |
645 | DBUG_PRINT("enter" , ("dropping events from %s" , db)); |
646 | |
647 | DBUG_SLOW_ASSERT(ok_for_lower_case_names(db)); |
648 | |
649 | /* |
650 | Sic: no check if the scheduler is disabled or system tables |
651 | are damaged, as intended. |
652 | */ |
653 | if (event_queue) |
654 | event_queue->drop_schema_events(thd, &db_lex); |
655 | db_repository->drop_schema_events(thd, &db_lex); |
656 | |
657 | DBUG_VOID_RETURN; |
658 | } |
659 | |
660 | |
661 | /** |
662 | A helper function to generate SHOW CREATE EVENT output from |
663 | a named event |
664 | */ |
665 | |
666 | static bool |
667 | send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol) |
668 | { |
669 | char show_str_buf[10 * STRING_BUFFER_USUAL_SIZE]; |
670 | String show_str(show_str_buf, sizeof(show_str_buf), system_charset_info); |
671 | List<Item> field_list; |
672 | LEX_CSTRING sql_mode; |
673 | const String *tz_name; |
674 | MEM_ROOT *mem_root= thd->mem_root; |
675 | DBUG_ENTER("send_show_create_event" ); |
676 | |
677 | show_str.length(0); |
678 | if (et->get_create_event(thd, &show_str)) |
679 | DBUG_RETURN(TRUE); |
680 | |
681 | field_list.push_back(new (mem_root) |
682 | Item_empty_string(thd, "Event" , NAME_CHAR_LEN), |
683 | mem_root); |
684 | |
685 | if (sql_mode_string_representation(thd, et->sql_mode, &sql_mode)) |
686 | DBUG_RETURN(TRUE); |
687 | |
688 | field_list.push_back(new (mem_root) |
689 | Item_empty_string(thd, "sql_mode" , |
690 | (uint) sql_mode.length), mem_root); |
691 | |
692 | tz_name= et->time_zone->get_name(); |
693 | |
694 | field_list.push_back(new (mem_root) |
695 | Item_empty_string(thd, "time_zone" , tz_name->length()), |
696 | mem_root); |
697 | |
698 | field_list.push_back(new (mem_root) |
699 | Item_empty_string(thd, "Create Event" , |
700 | show_str.length()), mem_root); |
701 | |
702 | field_list.push_back(new (mem_root) |
703 | Item_empty_string(thd, "character_set_client" , |
704 | MY_CS_NAME_SIZE), mem_root); |
705 | |
706 | field_list.push_back(new (mem_root) |
707 | Item_empty_string(thd, "collation_connection" , |
708 | MY_CS_NAME_SIZE), mem_root); |
709 | |
710 | field_list.push_back(new (mem_root) |
711 | Item_empty_string(thd, "Database Collation" , |
712 | MY_CS_NAME_SIZE), mem_root); |
713 | |
714 | if (protocol->send_result_set_metadata(&field_list, |
715 | Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) |
716 | DBUG_RETURN(TRUE); |
717 | |
718 | protocol->prepare_for_resend(); |
719 | |
720 | protocol->store(et->name.str, et->name.length, system_charset_info); |
721 | protocol->store(sql_mode.str, sql_mode.length, system_charset_info); |
722 | protocol->store(tz_name->ptr(), tz_name->length(), system_charset_info); |
723 | protocol->store(show_str.ptr(), show_str.length(), |
724 | et->creation_ctx->get_client_cs()); |
725 | protocol->store(et->creation_ctx->get_client_cs()->csname, |
726 | strlen(et->creation_ctx->get_client_cs()->csname), |
727 | system_charset_info); |
728 | protocol->store(et->creation_ctx->get_connection_cl()->name, |
729 | strlen(et->creation_ctx->get_connection_cl()->name), |
730 | system_charset_info); |
731 | protocol->store(et->creation_ctx->get_db_cl()->name, |
732 | strlen(et->creation_ctx->get_db_cl()->name), |
733 | system_charset_info); |
734 | |
735 | if (protocol->write()) |
736 | DBUG_RETURN(TRUE); |
737 | |
738 | my_eof(thd); |
739 | |
740 | DBUG_RETURN(FALSE); |
741 | } |
742 | |
743 | |
744 | /** |
745 | Implement SHOW CREATE EVENT statement |
746 | |
747 | thd Thread context |
748 | spn The name of the event (db, name) |
749 | |
750 | @retval FALSE OK |
751 | @retval TRUE error (reported) |
752 | */ |
753 | |
754 | bool |
755 | Events::show_create_event(THD *thd, const LEX_CSTRING *dbname, |
756 | const LEX_CSTRING *name) |
757 | { |
758 | Event_timed et; |
759 | bool ret; |
760 | |
761 | DBUG_ENTER("Events::show_create_event" ); |
762 | DBUG_PRINT("enter" , ("name: %s@%s" , dbname->str, name->str)); |
763 | |
764 | if (unlikely(check_if_system_tables_error())) |
765 | DBUG_RETURN(TRUE); |
766 | |
767 | if (check_access(thd, EVENT_ACL, dbname->str, NULL, NULL, 0, 0)) |
768 | DBUG_RETURN(TRUE); |
769 | |
770 | /* |
771 | We would like to allow SHOW CREATE EVENT under LOCK TABLES and |
772 | in pre-locked mode. mysql.event table is marked as a system table. |
773 | This flag reduces the set of its participation scenarios in LOCK TABLES |
774 | operation, and therefore an out-of-bound open of this table |
775 | for reading like the one below (sic, only for reading) is |
776 | more or less deadlock-free. For additional information about when a |
777 | deadlock can occur please refer to the description of 'system table' |
778 | flag. |
779 | */ |
780 | ret= db_repository->load_named_event(thd, dbname, name, &et); |
781 | |
782 | if (!ret) |
783 | ret= send_show_create_event(thd, &et, thd->protocol); |
784 | |
785 | DBUG_RETURN(ret); |
786 | } |
787 | |
788 | |
789 | /** |
790 | Check access rights and fill INFORMATION_SCHEMA.events table. |
791 | |
792 | @param[in,out] thd Thread context |
793 | @param[in] tables The temporary table to fill. |
794 | |
795 | In MySQL INFORMATION_SCHEMA tables are temporary tables that are |
796 | created and filled on demand. In this function, we fill |
797 | INFORMATION_SCHEMA.events. It is a callback for I_S module, invoked from |
798 | sql_show.cc |
799 | |
800 | @return Has to be integer, as such is the requirement of the I_S API |
801 | @retval 0 success |
802 | @retval 1 an error, pushed into the error stack |
803 | */ |
804 | |
805 | int |
806 | Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) |
807 | { |
808 | const char *db= NULL; |
809 | int ret; |
810 | char db_tmp[SAFE_NAME_LEN]; |
811 | DBUG_ENTER("Events::fill_schema_events" ); |
812 | |
813 | /* |
814 | If we didn't start events because of --skip-grant-tables, return an |
815 | empty set |
816 | */ |
817 | if (opt_noacl) |
818 | DBUG_RETURN(0); |
819 | |
820 | if (unlikely(check_if_system_tables_error())) |
821 | DBUG_RETURN(1); |
822 | |
823 | /* |
824 | If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to |
825 | be NULL. Let's do an assert anyway. |
826 | */ |
827 | if (thd->lex->sql_command == SQLCOM_SHOW_EVENTS) |
828 | { |
829 | DBUG_ASSERT(thd->lex->select_lex.db.str); |
830 | if (!is_infoschema_db(&thd->lex->select_lex.db) && // There is no events in I_S |
831 | check_access(thd, EVENT_ACL, thd->lex->select_lex.db.str, |
832 | NULL, NULL, 0, 0)) |
833 | DBUG_RETURN(1); |
834 | db= normalize_db_name(thd->lex->select_lex.db.str, db_tmp, sizeof(db_tmp)); |
835 | } |
836 | ret= db_repository->fill_schema_events(thd, tables, db); |
837 | |
838 | DBUG_RETURN(ret); |
839 | } |
840 | |
841 | |
842 | /** |
843 | Initializes the scheduler's structures. |
844 | |
845 | @param THD or null (if called by init) |
846 | @param opt_noacl_or_bootstrap |
847 | TRUE if there is --skip-grant-tables or --bootstrap |
848 | option. In that case we disable the event scheduler. |
849 | |
850 | @note This function is not synchronized. |
851 | |
852 | @retval FALSE Perhaps there was an error, and the event scheduler |
853 | is disabled. But the error is not fatal and the |
854 | server start up can continue. |
855 | @retval TRUE Fatal error. Startup must terminate (call unireg_abort()). |
856 | */ |
857 | |
858 | bool |
859 | Events::init(THD *thd, bool opt_noacl_or_bootstrap) |
860 | { |
861 | int err_no; |
862 | bool res= FALSE; |
863 | bool had_thd= thd != 0; |
864 | DBUG_ENTER("Events::init" ); |
865 | |
866 | DBUG_ASSERT(inited == 0); |
867 | |
868 | /* |
869 | Was disabled explicitly from the command line |
870 | */ |
871 | if (opt_event_scheduler == Events::EVENTS_DISABLED || |
872 | opt_noacl_or_bootstrap) |
873 | DBUG_RETURN(FALSE); |
874 | |
875 | /* We need a temporary THD during boot */ |
876 | if (!thd) |
877 | { |
878 | |
879 | if (!(thd= new THD(0))) |
880 | { |
881 | res= TRUE; |
882 | goto end; |
883 | } |
884 | /* |
885 | The thread stack does not start from this function but we cannot |
886 | guess the real value. So better some value that doesn't assert than |
887 | no value. |
888 | */ |
889 | thd->thread_stack= (char*) &thd; |
890 | thd->store_globals(); |
891 | /* |
892 | Set current time for the thread that handles events. |
893 | Current time is stored in data member start_time of THD class. |
894 | Subsequently, this value is used to check whether event was expired |
895 | when make loading events from storage. Check for event expiration time |
896 | is done at Event_queue_element::compute_next_execution_time() where |
897 | event's status set to Event_parse_data::DISABLED and dropped flag set |
898 | to true if event was expired. |
899 | */ |
900 | thd->set_time(); |
901 | } |
902 | |
903 | /* |
904 | We will need Event_db_repository anyway, even if the scheduler is |
905 | disabled - to perform events DDL. |
906 | */ |
907 | if (!(db_repository= new Event_db_repository)) |
908 | { |
909 | res= TRUE; /* fatal error: request unireg_abort */ |
910 | goto end; |
911 | } |
912 | |
913 | /* |
914 | Since we allow event DDL even if the scheduler is disabled, |
915 | check the system tables, as we might need them. |
916 | |
917 | If run with --skip-grant-tables or --bootstrap, don't try to do the |
918 | check of system tables and don't complain: in these modes the tables |
919 | are most likely not there and we're going to disable the event |
920 | scheduler anyway. |
921 | */ |
922 | if (Event_db_repository::check_system_tables(thd)) |
923 | { |
924 | delete db_repository; |
925 | db_repository= 0; |
926 | my_message(ER_STARTUP, |
927 | "Event Scheduler: An error occurred when initializing " |
928 | "system tables. Disabling the Event Scheduler." , |
929 | MYF(ME_NOREFRESH)); |
930 | /* Disable the scheduler since the system tables are not up to date */ |
931 | opt_event_scheduler= EVENTS_OFF; |
932 | goto end; |
933 | } |
934 | |
935 | |
936 | DBUG_ASSERT(opt_event_scheduler == Events::EVENTS_ON || |
937 | opt_event_scheduler == Events::EVENTS_OFF); |
938 | |
939 | if (!(event_queue= new Event_queue) || |
940 | !(scheduler= new Event_scheduler(event_queue))) |
941 | { |
942 | res= TRUE; /* fatal error: request unireg_abort */ |
943 | goto end; |
944 | } |
945 | |
946 | if (event_queue->init_queue(thd) || load_events_from_db(thd) || |
947 | (opt_event_scheduler == EVENTS_ON && scheduler->start(&err_no))) |
948 | { |
949 | my_message_sql(ER_STARTUP, |
950 | "Event Scheduler: Error while loading from mysql.event table." , |
951 | MYF(ME_NOREFRESH)); |
952 | res= TRUE; /* fatal error: request unireg_abort */ |
953 | goto end; |
954 | } |
955 | Event_worker_thread::init(db_repository); |
956 | inited= 1; |
957 | |
958 | end: |
959 | if (res) |
960 | deinit(); |
961 | if (!had_thd) |
962 | delete thd; |
963 | |
964 | DBUG_RETURN(res); |
965 | } |
966 | |
967 | /* |
968 | Cleans up scheduler's resources. Called at server shutdown. |
969 | |
970 | SYNOPSIS |
971 | Events::deinit() |
972 | |
973 | NOTES |
974 | This function is not synchronized. |
975 | */ |
976 | |
977 | void |
978 | Events::deinit() |
979 | { |
980 | DBUG_ENTER("Events::deinit" ); |
981 | |
982 | delete scheduler; |
983 | scheduler= NULL; /* For restart */ |
984 | delete event_queue; |
985 | event_queue= NULL; /* For restart */ |
986 | delete db_repository; |
987 | db_repository= NULL; /* For restart */ |
988 | |
989 | inited= 0; |
990 | DBUG_VOID_RETURN; |
991 | } |
992 | |
993 | #ifdef HAVE_PSI_INTERFACE |
994 | PSI_mutex_key key_LOCK_event_queue, |
995 | key_event_scheduler_LOCK_scheduler_state; |
996 | |
997 | static PSI_mutex_info all_events_mutexes[]= |
998 | { |
999 | { &key_LOCK_event_queue, "LOCK_event_queue" , PSI_FLAG_GLOBAL}, |
1000 | { &key_event_scheduler_LOCK_scheduler_state, "Event_scheduler::LOCK_scheduler_state" , PSI_FLAG_GLOBAL} |
1001 | }; |
1002 | |
1003 | PSI_cond_key key_event_scheduler_COND_state, key_COND_queue_state; |
1004 | |
1005 | static PSI_cond_info all_events_conds[]= |
1006 | { |
1007 | { &key_event_scheduler_COND_state, "Event_scheduler::COND_state" , PSI_FLAG_GLOBAL}, |
1008 | { &key_COND_queue_state, "COND_queue_state" , PSI_FLAG_GLOBAL}, |
1009 | }; |
1010 | |
1011 | PSI_thread_key key_thread_event_scheduler, key_thread_event_worker; |
1012 | |
1013 | static PSI_thread_info all_events_threads[]= |
1014 | { |
1015 | { &key_thread_event_scheduler, "event_scheduler" , PSI_FLAG_GLOBAL}, |
1016 | { &key_thread_event_worker, "event_worker" , 0} |
1017 | }; |
1018 | #endif /* HAVE_PSI_INTERFACE */ |
1019 | |
1020 | PSI_stage_info stage_waiting_on_empty_queue= { 0, "Waiting on empty queue" , 0}; |
1021 | PSI_stage_info stage_waiting_for_next_activation= { 0, "Waiting for next activation" , 0}; |
1022 | PSI_stage_info stage_waiting_for_scheduler_to_stop= { 0, "Waiting for the scheduler to stop" , 0}; |
1023 | |
1024 | #ifdef HAVE_PSI_INTERFACE |
1025 | PSI_stage_info *all_events_stages[]= |
1026 | { |
1027 | & stage_waiting_on_empty_queue, |
1028 | & stage_waiting_for_next_activation, |
1029 | & stage_waiting_for_scheduler_to_stop |
1030 | }; |
1031 | |
1032 | static void init_events_psi_keys(void) |
1033 | { |
1034 | const char* category= "sql" ; |
1035 | int count; |
1036 | |
1037 | count= array_elements(all_events_mutexes); |
1038 | mysql_mutex_register(category, all_events_mutexes, count); |
1039 | |
1040 | count= array_elements(all_events_conds); |
1041 | mysql_cond_register(category, all_events_conds, count); |
1042 | |
1043 | count= array_elements(all_events_threads); |
1044 | mysql_thread_register(category, all_events_threads, count); |
1045 | |
1046 | count= array_elements(all_events_stages); |
1047 | mysql_stage_register(category, all_events_stages, count); |
1048 | |
1049 | } |
1050 | #endif /* HAVE_PSI_INTERFACE */ |
1051 | |
1052 | /** |
1053 | Inits Events mutexes |
1054 | |
1055 | SYNOPSIS |
1056 | Events::init_mutexes() |
1057 | thd Thread |
1058 | */ |
1059 | |
1060 | void |
1061 | Events::init_mutexes() |
1062 | { |
1063 | #ifdef HAVE_PSI_INTERFACE |
1064 | init_events_psi_keys(); |
1065 | #endif |
1066 | } |
1067 | |
1068 | |
1069 | /* |
1070 | Dumps the internal status of the scheduler and the memory cache |
1071 | into a table with two columns - Name & Value. Different properties |
1072 | which could be useful for debugging for instance deadlocks are |
1073 | returned. |
1074 | |
1075 | SYNOPSIS |
1076 | Events::dump_internal_status() |
1077 | */ |
1078 | |
1079 | void |
1080 | Events::dump_internal_status() |
1081 | { |
1082 | DBUG_ENTER("Events::dump_internal_status" ); |
1083 | puts("\n\n\nEvents status:" ); |
1084 | puts("LLA = Last Locked At LUA = Last Unlocked At" ); |
1085 | puts("WOC = Waiting On Condition DL = Data Locked" ); |
1086 | |
1087 | /* |
1088 | opt_event_scheduler should only be accessed while |
1089 | holding LOCK_global_system_variables. |
1090 | */ |
1091 | mysql_mutex_lock(&LOCK_global_system_variables); |
1092 | if (!inited) |
1093 | puts("The Event Scheduler is disabled" ); |
1094 | else |
1095 | { |
1096 | scheduler->dump_internal_status(); |
1097 | event_queue->dump_internal_status(); |
1098 | } |
1099 | |
1100 | mysql_mutex_unlock(&LOCK_global_system_variables); |
1101 | DBUG_VOID_RETURN; |
1102 | } |
1103 | |
1104 | bool Events::start(int *err_no) |
1105 | { |
1106 | DBUG_ASSERT(inited); |
1107 | return scheduler->start(err_no); |
1108 | } |
1109 | |
1110 | bool Events::stop() |
1111 | { |
1112 | DBUG_ASSERT(inited); |
1113 | return scheduler->stop(); |
1114 | } |
1115 | |
1116 | /** |
1117 | Loads all ENABLED events from mysql.event into a prioritized |
1118 | queue. |
1119 | |
1120 | This function is called during the server start up. It reads |
1121 | every event, computes the next execution time, and if the event |
1122 | needs execution, adds it to a prioritized queue. Otherwise, if |
1123 | ON COMPLETION DROP is specified, the event is automatically |
1124 | removed from the table. |
1125 | |
1126 | @param[in,out] thd Thread context. Used for memory allocation in some cases. |
1127 | |
1128 | @retval FALSE success |
1129 | @retval TRUE error, the load is aborted |
1130 | |
1131 | @note Reports the error to the console |
1132 | */ |
1133 | |
1134 | bool |
1135 | Events::load_events_from_db(THD *thd) |
1136 | { |
1137 | TABLE *table; |
1138 | READ_RECORD read_record_info; |
1139 | bool ret= TRUE; |
1140 | uint count= 0; |
1141 | ulong saved_master_access; |
1142 | DBUG_ENTER("Events::load_events_from_db" ); |
1143 | DBUG_PRINT("enter" , ("thd: %p" , thd)); |
1144 | |
1145 | /* |
1146 | NOTE: even if we run in read-only mode, we should be able to lock the |
1147 | mysql.event table for writing. In order to achieve this, we should call |
1148 | mysql_lock_tables() under the super user. |
1149 | |
1150 | Same goes for transaction access mode. |
1151 | Temporarily reset it to read-write. |
1152 | */ |
1153 | |
1154 | saved_master_access= thd->security_ctx->master_access; |
1155 | thd->security_ctx->master_access |= SUPER_ACL; |
1156 | bool save_tx_read_only= thd->tx_read_only; |
1157 | thd->tx_read_only= false; |
1158 | |
1159 | ret= db_repository->open_event_table(thd, TL_WRITE, &table); |
1160 | |
1161 | thd->tx_read_only= save_tx_read_only; |
1162 | thd->security_ctx->master_access= saved_master_access; |
1163 | |
1164 | if (ret) |
1165 | { |
1166 | my_message_sql(ER_STARTUP, |
1167 | "Event Scheduler: Failed to open table mysql.event" , |
1168 | MYF(ME_NOREFRESH)); |
1169 | DBUG_RETURN(TRUE); |
1170 | } |
1171 | |
1172 | if (init_read_record(&read_record_info, thd, table, NULL, NULL, 0, 1, FALSE)) |
1173 | { |
1174 | close_thread_tables(thd); |
1175 | DBUG_RETURN(TRUE); |
1176 | } |
1177 | |
1178 | while (!(read_record_info.read_record())) |
1179 | { |
1180 | Event_queue_element *et; |
1181 | bool created, dropped; |
1182 | |
1183 | if (!(et= new Event_queue_element)) |
1184 | goto end; |
1185 | |
1186 | DBUG_PRINT("info" , ("Loading event from row." )); |
1187 | |
1188 | if (et->load_from_row(thd, table)) |
1189 | { |
1190 | my_message(ER_STARTUP, |
1191 | "Event Scheduler: " |
1192 | "Error while loading events from mysql.event. " |
1193 | "The table probably contains bad data or is corrupted" , |
1194 | MYF(ME_NOREFRESH)); |
1195 | delete et; |
1196 | goto end; |
1197 | } |
1198 | /** |
1199 | Since the Event_queue_element object could be deleted inside |
1200 | Event_queue::create_event we should save the value of dropped flag |
1201 | into the temporary variable. |
1202 | */ |
1203 | dropped= et->dropped; |
1204 | if (event_queue->create_event(thd, et, &created)) |
1205 | { |
1206 | /* Out of memory */ |
1207 | delete et; |
1208 | goto end; |
1209 | } |
1210 | if (created) |
1211 | count++; |
1212 | else if (dropped) |
1213 | { |
1214 | /* |
1215 | If not created, a stale event - drop if immediately if |
1216 | ON COMPLETION NOT PRESERVE. |
1217 | XXX: This won't be replicated, thus the drop won't appear in |
1218 | in the slave. When the slave is restarted it will drop events. |
1219 | However, as the slave will be "out of sync", it might happen that |
1220 | an event created on the master, after master restart, won't be |
1221 | replicated to the slave correctly, as the create will fail there. |
1222 | */ |
1223 | int rc= table->file->ha_delete_row(table->record[0]); |
1224 | if (rc) |
1225 | { |
1226 | table->file->print_error(rc, MYF(0)); |
1227 | goto end; |
1228 | } |
1229 | } |
1230 | } |
1231 | my_printf_error(ER_STARTUP, |
1232 | "Event Scheduler: Loaded %d event%s" , |
1233 | MYF(ME_NOREFRESH | |
1234 | (global_system_variables.log_warnings) ? |
1235 | ME_JUST_INFO: 0), |
1236 | count, (count == 1) ? "" : "s" ); |
1237 | ret= FALSE; |
1238 | |
1239 | end: |
1240 | end_read_record(&read_record_info); |
1241 | |
1242 | close_mysql_tables(thd); |
1243 | DBUG_RETURN(ret); |
1244 | } |
1245 | |
1246 | #ifdef WITH_WSREP |
1247 | int wsrep_create_event_query(THD *thd, uchar** buf, size_t* buf_len) |
1248 | { |
1249 | char buffer[1024]; |
1250 | String log_query(buffer, sizeof(buffer), &my_charset_bin); |
1251 | |
1252 | if (create_query_string(thd, &log_query)) |
1253 | { |
1254 | WSREP_WARN("events create string failed: schema: %s, query: %s" , |
1255 | thd->get_db(), thd->query()); |
1256 | return 1; |
1257 | } |
1258 | return wsrep_to_buf_helper(thd, log_query.ptr(), log_query.length(), buf, buf_len); |
1259 | } |
1260 | #endif /* WITH_WSREP */ |
1261 | /** |
1262 | @} (End of group Event_Scheduler) |
1263 | */ |
1264 | |