1 /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software Foundation,
21    51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22 
23 /* This implements 'user defined functions' */
24 
25 /*
26    Known bugs:
27 
28    Memory for functions is never freed!
29    Shared libraries are not closed before mysqld exits;
30      - This is because we can't be sure if some threads are using
31        a function.
32 
33    The bugs only affect applications that create and free a lot of
34    dynamic functions, so this shouldn't be a real problem.
35 */
36 
37 #include "sql_base.h"                           // close_mysql_tables
38 #include "sql_parse.h"                        // check_string_char_length
39 #include "sql_table.h"                        // write_bin_log
40 #include "records.h"          // init_read_record, end_read_record
41 #include "lock.h"                               // MYSQL_LOCK_IGNORE_TIMEOUT
42 #include "log.h"
43 #include "sql_plugin.h"                         // check_valid_path
44 
45 #ifdef HAVE_DLFCN_H
46 #include <dlfcn.h>
47 #endif
48 
49 #ifdef HAVE_DLOPEN
50 #include <stdarg.h>
51 #include <hash.h>
52 
53 static bool initialized = 0;
54 static MEM_ROOT mem;
55 static HASH udf_hash;
56 static mysql_rwlock_t THR_LOCK_udf;
57 
58 
59 static udf_func *add_udf(LEX_STRING *name, Item_result ret,
60                          char *dl, Item_udftype typ);
61 static void del_udf(udf_func *udf);
62 static void *find_udf_dl(const char *dl);
63 
init_syms(udf_func * tmp,char * nm)64 static char *init_syms(udf_func *tmp, char *nm)
65 {
66   char *end;
67 
68   if (!((tmp->func= (Udf_func_any) dlsym(tmp->dlhandle, tmp->name.str))))
69     return tmp->name.str;
70 
71   end=my_stpcpy(nm,tmp->name.str);
72 
73   if (tmp->type == UDFTYPE_AGGREGATE)
74   {
75     (void)my_stpcpy(end, "_clear");
76     if (!((tmp->func_clear= (Udf_func_clear) dlsym(tmp->dlhandle, nm))))
77       return nm;
78     (void)my_stpcpy(end, "_add");
79     if (!((tmp->func_add= (Udf_func_add) dlsym(tmp->dlhandle, nm))))
80       return nm;
81   }
82 
83   (void) my_stpcpy(end,"_deinit");
84   tmp->func_deinit= (Udf_func_deinit) dlsym(tmp->dlhandle, nm);
85 
86   (void) my_stpcpy(end,"_init");
87   tmp->func_init= (Udf_func_init) dlsym(tmp->dlhandle, nm);
88 
89   /*
90     to prefent loading "udf" from, e.g. libc.so
91     let's ensure that at least one auxiliary symbol is defined
92   */
93   if (!tmp->func_init && !tmp->func_deinit && tmp->type != UDFTYPE_AGGREGATE)
94   {
95     if (!opt_allow_suspicious_udfs)
96       return nm;
97     sql_print_warning(ER(ER_CANT_FIND_DL_ENTRY), nm);
98   }
99   return 0;
100 }
101 
102 
get_hash_key(const uchar * buff,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))103 extern "C" uchar* get_hash_key(const uchar *buff, size_t *length,
104 			      my_bool not_used MY_ATTRIBUTE((unused)))
105 {
106   udf_func *udf=(udf_func*) buff;
107   *length=(uint) udf->name.length;
108   return (uchar*) udf->name.str;
109 }
110 
111 static PSI_memory_key key_memory_udf_mem;
112 
113 #ifdef HAVE_PSI_INTERFACE
114 static PSI_rwlock_key key_rwlock_THR_LOCK_udf;
115 
116 static PSI_rwlock_info all_udf_rwlocks[]=
117 {
118   { &key_rwlock_THR_LOCK_udf, "THR_LOCK_udf", PSI_FLAG_GLOBAL}
119 };
120 
121 static PSI_memory_info all_udf_memory[]=
122 {
123   { &key_memory_udf_mem, "udf_mem", PSI_FLAG_GLOBAL}
124 };
125 
init_udf_psi_keys(void)126 static void init_udf_psi_keys(void)
127 {
128   const char* category= "sql";
129   int count;
130 
131   count= array_elements(all_udf_rwlocks);
132   mysql_rwlock_register(category, all_udf_rwlocks, count);
133 
134   count= array_elements(all_udf_memory);
135   mysql_memory_register(category, all_udf_memory, count);
136 }
137 #endif
138 
139 /*
140   Read all predeclared functions from mysql.func and accept all that
141   can be used.
142 */
143 
udf_init()144 void udf_init()
145 {
146   udf_func *tmp;
147   TABLE_LIST tables;
148   READ_RECORD read_record_info;
149   TABLE *table;
150   int error;
151   DBUG_ENTER("ufd_init");
152   char db[]= "mysql"; /* A subject to casednstr, can't be constant */
153 
154   if (initialized)
155     DBUG_VOID_RETURN;
156 
157 #ifdef HAVE_PSI_INTERFACE
158   init_udf_psi_keys();
159 #endif
160 
161   mysql_rwlock_init(key_rwlock_THR_LOCK_udf, &THR_LOCK_udf);
162 
163   init_sql_alloc(key_memory_udf_mem, &mem, UDF_ALLOC_BLOCK_SIZE, 0);
164   THD *new_thd = new THD;
165   if (!new_thd ||
166       my_hash_init(&udf_hash,system_charset_info,32,0,0,get_hash_key, NULL, 0,
167                    key_memory_udf_mem))
168   {
169     sql_print_error("Can't allocate memory for udf structures");
170     my_hash_free(&udf_hash);
171     free_root(&mem,MYF(0));
172     delete new_thd;
173     DBUG_VOID_RETURN;
174   }
175   initialized = 1;
176   new_thd->thread_stack= (char*) &new_thd;
177   new_thd->store_globals();
178   {
179     LEX_CSTRING db_lex_cstr= { STRING_WITH_LEN(db) };
180     new_thd->set_db(db_lex_cstr);
181   }
182 
183   tables.init_one_table(db, sizeof(db)-1, "func", 4, "func", TL_READ);
184 
185   if (open_and_lock_tables(new_thd, &tables, MYSQL_LOCK_IGNORE_TIMEOUT))
186   {
187     DBUG_PRINT("error",("Can't open udf table"));
188     sql_print_error("Can't open the mysql.func table. Please "
189                     "run mysql_upgrade to create it.");
190     goto end;
191   }
192 
193   table= tables.table;
194   if (init_read_record(&read_record_info, new_thd, table, NULL, 1, 1, FALSE))
195     goto end;
196   table->use_all_columns();
197   while (!(error= read_record_info.read_record(&read_record_info)))
198   {
199     DBUG_PRINT("info",("init udf record"));
200     LEX_STRING name;
201     name.str=get_field(&mem, table->field[0]);
202     name.length = strlen(name.str);
203     char *dl_name= get_field(&mem, table->field[2]);
204     bool new_dl=0;
205     Item_udftype udftype=UDFTYPE_FUNCTION;
206     if (table->s->fields >= 4)			// New func table
207       udftype=(Item_udftype) table->field[3]->val_int();
208 
209     /*
210       Ensure that the .dll doesn't have a path
211       This is done to ensure that only approved dll from the system
212       directories are used (to make this even remotely secure).
213 
214       On windows we must check both FN_LIBCHAR and '/'.
215     */
216 
217     LEX_CSTRING name_cstr= {name.str, name.length};
218     if (check_valid_path(dl_name, strlen(dl_name)) ||
219         check_string_char_length(name_cstr, "", NAME_CHAR_LEN,
220                                  system_charset_info, 1))
221     {
222       sql_print_error("Invalid row in mysql.func table for function '%.64s'",
223                       name.str);
224       continue;
225     }
226 
227     if (!(tmp= add_udf(&name,(Item_result) table->field[1]->val_int(),
228                        dl_name, udftype)))
229     {
230       sql_print_error("Can't alloc memory for udf function: '%.64s'", name.str);
231       continue;
232     }
233 
234     void *dl = find_udf_dl(tmp->dl);
235     if (dl == NULL)
236     {
237       char dlpath[FN_REFLEN];
238       strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", tmp->dl,
239                NullS);
240       (void) unpack_filename(dlpath, dlpath);
241       if (!(dl= dlopen(dlpath, RTLD_NOW)))
242       {
243 	const char *errmsg;
244 	int error_number= dlopen_errno;
245 	DLERROR_GENERATE(errmsg, error_number);
246 
247 	/* Print warning to log */
248         sql_print_error(ER(ER_CANT_OPEN_LIBRARY), tmp->dl, error_number, errmsg);
249 	/* Keep the udf in the hash so that we can remove it later */
250 	continue;
251       }
252       new_dl=1;
253     }
254     tmp->dlhandle = dl;
255     {
256       char buf[NAME_LEN+16], *missing;
257       if ((missing= init_syms(tmp, buf)))
258       {
259         sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), missing);
260         del_udf(tmp);
261         if (new_dl)
262           dlclose(dl);
263       }
264     }
265   }
266   if (error > 0)
267     sql_print_error("Got unknown error: %d", my_errno());
268   end_read_record(&read_record_info);
269   table->m_needs_reopen= TRUE;                  // Force close to free memory
270 
271 end:
272   close_mysql_tables(new_thd);
273   delete new_thd;
274   DBUG_VOID_RETURN;
275 }
276 
277 
udf_free()278 void udf_free()
279 {
280   /* close all shared libraries */
281   DBUG_ENTER("udf_free");
282   for (uint idx=0 ; idx < udf_hash.records ; idx++)
283   {
284     udf_func *udf=(udf_func*) my_hash_element(&udf_hash,idx);
285     if (udf->dlhandle)				// Not closed before
286     {
287       /* Mark all versions using the same handler as closed */
288       for (uint j=idx+1 ;  j < udf_hash.records ; j++)
289       {
290 	udf_func *tmp=(udf_func*) my_hash_element(&udf_hash,j);
291 	if (udf->dlhandle == tmp->dlhandle)
292 	  tmp->dlhandle=0;			// Already closed
293       }
294       dlclose(udf->dlhandle);
295     }
296   }
297   my_hash_free(&udf_hash);
298   free_root(&mem,MYF(0));
299   if (initialized)
300   {
301     initialized= 0;
302     mysql_rwlock_destroy(&THR_LOCK_udf);
303   }
304   DBUG_VOID_RETURN;
305 }
306 
307 
del_udf(udf_func * udf)308 static void del_udf(udf_func *udf)
309 {
310   DBUG_ENTER("del_udf");
311   if (!--udf->usage_count)
312   {
313     my_hash_delete(&udf_hash,(uchar*) udf);
314     using_udf_functions=udf_hash.records != 0;
315   }
316   else
317   {
318     /*
319       The functions is in use ; Rename the functions instead of removing it.
320       The functions will be automaticly removed when the least threads
321       doesn't use it anymore
322     */
323     char *name= udf->name.str;
324     size_t name_length=udf->name.length;
325     udf->name.str=(char*) "*";
326     udf->name.length=1;
327     my_hash_update(&udf_hash,(uchar*) udf,(uchar*) name,name_length);
328   }
329   DBUG_VOID_RETURN;
330 }
331 
332 
free_udf(udf_func * udf)333 void free_udf(udf_func *udf)
334 {
335   DBUG_ENTER("free_udf");
336 
337   if (!initialized)
338     DBUG_VOID_RETURN;
339 
340   mysql_rwlock_wrlock(&THR_LOCK_udf);
341   if (!--udf->usage_count)
342   {
343     /*
344       We come here when someone has deleted the udf function
345       while another thread still was using the udf
346     */
347     my_hash_delete(&udf_hash,(uchar*) udf);
348     using_udf_functions=udf_hash.records != 0;
349     if (!find_udf_dl(udf->dl))
350       dlclose(udf->dlhandle);
351   }
352   mysql_rwlock_unlock(&THR_LOCK_udf);
353   DBUG_VOID_RETURN;
354 }
355 
356 
357 /* This is only called if using_udf_functions != 0 */
358 
find_udf(const char * name,size_t length,bool mark_used)359 udf_func *find_udf(const char *name, size_t length,bool mark_used)
360 {
361   udf_func *udf=0;
362   DBUG_ENTER("find_udf");
363 
364   if (!initialized)
365     DBUG_RETURN(NULL);
366 
367   /* TODO: This should be changed to reader locks someday! */
368   if (mark_used)
369     mysql_rwlock_wrlock(&THR_LOCK_udf);  /* Called during fix_fields */
370   else
371     mysql_rwlock_rdlock(&THR_LOCK_udf);  /* Called during parsing */
372 
373   if ((udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) name,
374                                       length ? length : strlen(name))))
375   {
376     if (!udf->dlhandle)
377       udf=0;					// Could not be opened
378     else if (mark_used)
379       udf->usage_count++;
380   }
381   mysql_rwlock_unlock(&THR_LOCK_udf);
382   DBUG_RETURN(udf);
383 }
384 
385 
find_udf_dl(const char * dl)386 static void *find_udf_dl(const char *dl)
387 {
388   DBUG_ENTER("find_udf_dl");
389 
390   /*
391     Because only the function name is hashed, we have to search trough
392     all rows to find the dl.
393   */
394   for (uint idx=0 ; idx < udf_hash.records ; idx++)
395   {
396     udf_func *udf=(udf_func*) my_hash_element(&udf_hash,idx);
397     if (!strcmp(dl, udf->dl) && udf->dlhandle != NULL)
398       DBUG_RETURN(udf->dlhandle);
399   }
400   DBUG_RETURN(0);
401 }
402 
403 
404 /* Assume that name && dl is already allocated */
405 
add_udf(LEX_STRING * name,Item_result ret,char * dl,Item_udftype type)406 static udf_func *add_udf(LEX_STRING *name, Item_result ret, char *dl,
407 			 Item_udftype type)
408 {
409   if (!name || !dl || !(uint) type || (uint) type > (uint) UDFTYPE_AGGREGATE)
410     return 0;
411   udf_func *tmp= (udf_func*) alloc_root(&mem, sizeof(udf_func));
412   if (!tmp)
413     return 0;
414   memset(tmp, 0, sizeof(*tmp));
415   tmp->name = *name; //dup !!
416   tmp->dl = dl;
417   tmp->returns = ret;
418   tmp->type = type;
419   tmp->usage_count=1;
420   if (my_hash_insert(&udf_hash,(uchar*)  tmp))
421     return 0;
422   using_udf_functions=1;
423   return tmp;
424 }
425 
426 
427 /**
428   Create a user defined function.
429 
430   @note Like implementations of other DDL/DML in MySQL, this function
431   relies on the caller to close the thread tables. This is done in the
432   end of dispatch_command().
433 */
434 
mysql_create_function(THD * thd,udf_func * udf)435 int mysql_create_function(THD *thd,udf_func *udf)
436 {
437   int error;
438   void *dl=0;
439   bool new_dl=0;
440   TABLE *table;
441   TABLE_LIST tables;
442   udf_func *u_d;
443   bool save_binlog_row_based;
444   DBUG_ENTER("mysql_create_function");
445 
446   if (!initialized)
447   {
448     if (opt_noacl)
449       my_error(ER_CANT_INITIALIZE_UDF, MYF(0),
450                udf->name.str,
451                "UDFs are unavailable with the --skip-grant-tables option");
452     else
453       my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
454     DBUG_RETURN(1);
455   }
456 
457   /*
458     Ensure that the .dll doesn't have a path
459     This is done to ensure that only approved dll from the system
460     directories are used (to make this even remotely secure).
461   */
462   if (check_valid_path(udf->dl, strlen(udf->dl)))
463   {
464     my_message(ER_UDF_NO_PATHS, ER(ER_UDF_NO_PATHS), MYF(0));
465     DBUG_RETURN(1);
466   }
467   LEX_CSTRING udf_name_cstr= {udf->name.str, udf->name.length};
468   if (check_string_char_length(udf_name_cstr, "", NAME_CHAR_LEN,
469                                system_charset_info, 1))
470   {
471     my_error(ER_TOO_LONG_IDENT, MYF(0), udf->name.str);
472     DBUG_RETURN(1);
473   }
474 
475   tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE);
476   if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
477     DBUG_RETURN(1);
478 
479   /*
480     Turn off row binlogging of this statement and use statement-based
481     so that all supporting tables are updated for CREATE FUNCTION command.
482   */
483   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
484     thd->clear_current_stmt_binlog_format_row();
485 
486   mysql_rwlock_wrlock(&THR_LOCK_udf);
487   if ((my_hash_search(&udf_hash,(uchar*) udf->name.str, udf->name.length)))
488   {
489     my_error(ER_UDF_EXISTS, MYF(0), udf->name.str);
490     goto err;
491   }
492   if (!(dl = find_udf_dl(udf->dl)))
493   {
494     char dlpath[FN_REFLEN];
495     strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", udf->dl, NullS);
496     (void) unpack_filename(dlpath, dlpath);
497 
498     if (!(dl = dlopen(dlpath, RTLD_NOW)))
499     {
500       const char *errmsg;
501       int error_number= dlopen_errno;
502       DLERROR_GENERATE(errmsg, error_number);
503 
504       DBUG_PRINT("error",("dlopen of %s failed, error: %d (%s)",
505                           udf->dl, error_number, errmsg));
506       my_error(ER_CANT_OPEN_LIBRARY, MYF(0),
507                udf->dl, error_number, errmsg);
508       goto err;
509     }
510     new_dl=1;
511   }
512   udf->dlhandle=dl;
513   {
514     char buf[NAME_LEN+16], *missing;
515     if ((missing= init_syms(udf, buf)))
516     {
517       my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), missing);
518       goto err;
519     }
520   }
521   udf->name.str=strdup_root(&mem,udf->name.str);
522   udf->dl=strdup_root(&mem,udf->dl);
523   if (!(u_d=add_udf(&udf->name,udf->returns,udf->dl,udf->type)))
524     goto err;
525   u_d->dlhandle = dl;
526   u_d->func=udf->func;
527   u_d->func_init=udf->func_init;
528   u_d->func_deinit=udf->func_deinit;
529   u_d->func_clear=udf->func_clear;
530   u_d->func_add=udf->func_add;
531 
532   /* create entry in mysql.func table */
533 
534   table->use_all_columns();
535   restore_record(table, s->default_values);	// Default values for fields
536   table->field[0]->store(u_d->name.str, u_d->name.length, system_charset_info);
537   table->field[1]->store((longlong) u_d->returns, TRUE);
538   table->field[2]->store(u_d->dl, strlen(u_d->dl), system_charset_info);
539   if (table->s->fields >= 4)			// If not old func format
540     table->field[3]->store((longlong) u_d->type, TRUE);
541   error = table->file->ha_write_row(table->record[0]);
542 
543   if (error)
544   {
545     char errbuf[MYSYS_STRERROR_SIZE];
546     my_error(ER_ERROR_ON_WRITE, MYF(0), "mysql.func", error,
547              my_strerror(errbuf, sizeof(errbuf), error));
548     del_udf(u_d);
549     goto err;
550   }
551   mysql_rwlock_unlock(&THR_LOCK_udf);
552 
553   /* Binlog the create function. */
554   if (write_bin_log(thd, true, thd->query().str, thd->query().length))
555   {
556     /* Restore the state of binlog format */
557     DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
558     if (save_binlog_row_based)
559       thd->set_current_stmt_binlog_format_row();
560     DBUG_RETURN(1);
561   }
562   /* Restore the state of binlog format */
563   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
564   if (save_binlog_row_based)
565     thd->set_current_stmt_binlog_format_row();
566   DBUG_RETURN(0);
567 
568  err:
569   if (new_dl)
570     dlclose(dl);
571   mysql_rwlock_unlock(&THR_LOCK_udf);
572   /* Restore the state of binlog format */
573   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
574   if (save_binlog_row_based)
575     thd->set_current_stmt_binlog_format_row();
576   DBUG_RETURN(1);
577 }
578 
579 
mysql_drop_function(THD * thd,const LEX_STRING * udf_name)580 int mysql_drop_function(THD *thd,const LEX_STRING *udf_name)
581 {
582   TABLE *table;
583   TABLE_LIST tables;
584   udf_func *udf;
585   char *exact_name_str;
586   size_t exact_name_len;
587   bool save_binlog_row_based;
588   int error= 1;
589   DBUG_ENTER("mysql_drop_function");
590 
591   if (!initialized)
592   {
593     if (opt_noacl)
594       my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str);
595     else
596       my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
597     DBUG_RETURN(1);
598   }
599 
600   tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE);
601   if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
602     DBUG_RETURN(1);
603 
604   /*
605     Turn off row binlogging of this statement and use statement-based
606     so that all supporting tables are updated for DROP FUNCTION command.
607   */
608   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
609     thd->clear_current_stmt_binlog_format_row();
610 
611   mysql_rwlock_wrlock(&THR_LOCK_udf);
612   if (!(udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) udf_name->str,
613                                        (uint) udf_name->length)))
614   {
615     my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str);
616     mysql_rwlock_unlock(&THR_LOCK_udf);
617     goto exit;
618   }
619   exact_name_str= udf->name.str;
620   exact_name_len= udf->name.length;
621   del_udf(udf);
622   /*
623     Close the handle if this was function that was found during boot or
624     CREATE FUNCTION and it's not in use by any other udf function
625   */
626   if (udf->dlhandle && !find_udf_dl(udf->dl))
627     dlclose(udf->dlhandle);
628   mysql_rwlock_unlock(&THR_LOCK_udf);
629 
630   table->use_all_columns();
631   table->field[0]->store(exact_name_str, exact_name_len, &my_charset_bin);
632   if (!table->file->ha_index_read_idx_map(table->record[0], 0,
633                                           table->field[0]->ptr,
634                                           HA_WHOLE_KEY,
635                                           HA_READ_KEY_EXACT))
636   {
637     int delete_err;
638     if ((delete_err = table->file->ha_delete_row(table->record[0])))
639       table->file->print_error(delete_err, MYF(0));
640   }
641 
642   /*
643     Binlog the drop function. Keep the table open and locked
644     while binlogging, to avoid binlog inconsistency.
645   */
646   if (!write_bin_log(thd, true, thd->query().str, thd->query().length))
647     error= 0;
648 exit:
649   /* Restore the state of binlog format */
650   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
651   if (save_binlog_row_based)
652     thd->set_current_stmt_binlog_format_row();
653   DBUG_RETURN(error);
654 }
655 
656 #endif /* HAVE_DLOPEN */
657