~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to sql/sql_udf.cc

  • Committer: brian
  • Date: 2008-06-25 05:29:13 UTC
  • Revision ID: brian@localhost.localdomain-20080625052913-6upwo0jsrl4lnapl
clean slate

Show diffs side-by-side

added added

removed removed

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