ac37a1b49618b8ab25d9550bab05672868808c33
[oweals/gnunet.git] / src / mysql / mysql.c
1 /*
2      This file is part of GNUnet
3      (C) 2012 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file mysql/mysql.c
22  * @brief library to help with access to a MySQL database
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include <mysql/mysql.h>
27 #include "gnunet_mysql_lib.h"
28
29 /**
30  * Maximum number of supported parameters for a prepared
31  * statement.  Increase if needed.
32  */
33 #define MAX_PARAM 16
34
35
36 /**
37  * Die with an error message that indicates
38  * a failure of the command 'cmd' with the message given
39  * by strerror(errno).
40  */
41 #define DIE_MYSQL(cmd, dbh) do { GNUNET_log_from (GNUNET_ERROR_TYPE__ERROR, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); GNUNET_abort(); } while(0);
42
43 /**
44  * Log an error message at log-level 'level' that indicates
45  * a failure of the command 'cmd' on file 'filename'
46  * with the message given by strerror(errno).
47  */
48 #define LOG_MYSQL(level, cmd, dbh) do { GNUNET_log_from (level, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); } while(0);
49
50
51 /**
52  * Mysql context.
53  */
54 struct GNUNET_MYSQL_Context
55 {
56   
57   /**
58    * Our configuration.
59    */
60   const struct GNUNET_CONFIGURATION_Handle *cfg;
61   
62   /**
63    * Our section.
64    */
65   const char *section;
66
67   /**
68    * Handle to the mysql database.
69    */
70   MYSQL *dbf;
71
72   /**
73    * Head of list of our prepared statements.
74    */
75   struct GNUNET_MYSQL_StatementHandle *shead;
76
77   /**
78    * Tail of list of our prepared statements.
79    */
80   struct GNUNET_MYSQL_StatementHandle *stail;
81
82   /**
83    * Filename of "my.cnf" (msyql configuration).
84    */
85   char *cnffile;
86
87 };
88
89
90 /**
91  * Handle for a prepared statement.
92  */
93 struct GNUNET_MYSQL_StatementHandle
94 {
95
96   /**
97    * Kept in a DLL.
98    */
99   struct GNUNET_MYSQL_StatementHandle *next;
100
101   /**
102    * Kept in a DLL.
103    */
104   struct GNUNET_MYSQL_StatementHandle *prev;
105
106   /**
107    * Original query string.
108    */
109   char *query;
110
111   /**
112    * Handle to MySQL prepared statement.
113    */
114   MYSQL_STMT *statement;
115
116   /**
117    * Is the MySQL prepared statement valid, or do we need to re-initialize it?
118    */
119   int valid;
120
121 };
122
123
124 /**
125  * Obtain the location of ".my.cnf".
126  *
127  * @param cfg our configuration
128  * @return NULL on error
129  */
130 static char *
131 get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg,
132                  const char *section)
133 {
134   char *cnffile;
135   char *home_dir;
136   struct stat st;
137
138 #ifndef WINDOWS
139   struct passwd *pw;
140 #endif
141   int configured;
142
143 #ifndef WINDOWS
144   pw = getpwuid (getuid ());
145   if (!pw)
146   {
147     GNUNET_log_from_strerror (GNUNET_ERROR_TYPE_ERROR, "mysql", "getpwuid");
148     return NULL;
149   }
150   if (GNUNET_YES ==
151       GNUNET_CONFIGURATION_have_value (cfg, section, "CONFIG"))
152   {
153     GNUNET_assert (GNUNET_OK ==
154                    GNUNET_CONFIGURATION_get_value_filename (cfg,
155                                                             section,
156                                                             "CONFIG",
157                                                             &cnffile));
158     configured = GNUNET_YES;
159   }
160   else
161   {
162     home_dir = GNUNET_strdup (pw->pw_dir);
163     GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
164     GNUNET_free (home_dir);
165     configured = GNUNET_NO;
166   }
167 #else
168   home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
169   plibc_conv_to_win_path ("~/", home_dir);
170   GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
171   GNUNET_free (home_dir);
172   configured = GNUNET_NO;
173 #endif
174   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
175                    "mysql",
176                    _("Trying to use file `%s' for MySQL configuration.\n"), cnffile);
177   if ((0 != STAT (cnffile, &st)) || (0 != ACCESS (cnffile, R_OK)) ||
178       (!S_ISREG (st.st_mode)))
179   {
180     if (configured == GNUNET_YES)
181       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
182                        "mysql",
183                        _("Could not access file `%s': %s\n"), cnffile,
184                        STRERROR (errno));
185     GNUNET_free (cnffile);
186     return NULL;
187   }
188   return cnffile;
189 }
190
191
192 /**
193  * Open the connection with the database (and initialize
194  * our default options).
195  *
196  * @param mc database context to initialze
197  * @return GNUNET_OK on success
198  */
199 static int
200 iopen (struct GNUNET_MYSQL_Context *mc)
201 {
202   char *mysql_dbname;
203   char *mysql_server;
204   char *mysql_user;
205   char *mysql_password;
206   unsigned long long mysql_port;
207   my_bool reconnect;
208   unsigned int timeout;
209
210   mc->dbf = mysql_init (NULL);
211   if (mc->dbf == NULL)
212     return GNUNET_SYSERR;
213   if (mc->cnffile != NULL)
214     mysql_options (mc->dbf, MYSQL_READ_DEFAULT_FILE, mc->cnffile);
215   mysql_options (mc->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
216   reconnect = 0;
217   mysql_options (mc->dbf, MYSQL_OPT_RECONNECT, &reconnect);
218   mysql_options (mc->dbf, MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
219   mysql_options (mc->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
220   timeout = 60;                 /* in seconds */
221   mysql_options (mc->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
222   mysql_options (mc->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
223   mysql_dbname = NULL;
224   if (GNUNET_YES ==
225       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
226                                        "DATABASE"))
227     GNUNET_assert (GNUNET_OK ==
228                    GNUNET_CONFIGURATION_get_value_string (mc->cfg,
229                                                           mc->section,
230                                                           "DATABASE",
231                                                           &mysql_dbname));
232   else
233     mysql_dbname = GNUNET_strdup ("gnunet");
234   mysql_user = NULL;
235   if (GNUNET_YES ==
236       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
237                                        "USER"))
238   {
239     GNUNET_assert (GNUNET_OK ==
240                    GNUNET_CONFIGURATION_get_value_string (mc->cfg,
241                                                           mc->section,
242                                                           "USER", &mysql_user));
243   }
244   mysql_password = NULL;
245   if (GNUNET_YES ==
246       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
247                                        "PASSWORD"))
248   {
249     GNUNET_assert (GNUNET_OK ==
250                    GNUNET_CONFIGURATION_get_value_string (mc->cfg,
251                                                           mc->section,
252                                                           "PASSWORD",
253                                                           &mysql_password));
254   }
255   mysql_server = NULL;
256   if (GNUNET_YES ==
257       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
258                                        "HOST"))
259   {
260     GNUNET_assert (GNUNET_OK ==
261                    GNUNET_CONFIGURATION_get_value_string (mc->cfg,
262                                                           mc->section,
263                                                           "HOST",
264                                                           &mysql_server));
265   }
266   mysql_port = 0;
267   if (GNUNET_YES ==
268       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
269                                        "PORT"))
270   {
271     GNUNET_assert (GNUNET_OK ==
272                    GNUNET_CONFIGURATION_get_value_number (mc->cfg,
273                                                           mc->section,
274                                                           "PORT", &mysql_port));
275   }
276
277   GNUNET_assert (mysql_dbname != NULL);
278   mysql_real_connect (mc->dbf, mysql_server, mysql_user, mysql_password,
279                       mysql_dbname, (unsigned int) mysql_port, NULL,
280                       CLIENT_IGNORE_SIGPIPE);
281   GNUNET_free_non_null (mysql_server);
282   GNUNET_free_non_null (mysql_user);
283   GNUNET_free_non_null (mysql_password);
284   GNUNET_free (mysql_dbname);
285   if (mysql_error (mc->dbf)[0])
286   {
287     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_real_connect", mc);
288     return GNUNET_SYSERR;
289   }
290   return GNUNET_OK;
291 }
292
293
294 /**
295  * Create a mysql context.
296  *
297  * @param cfg configuration
298  * @param section configuration section to use to get MySQL configuration options
299  * @return the mysql context
300  */
301 struct GNUNET_MYSQL_Context *
302 GNUNET_MYSQL_context_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
303                              const char *section)
304 {
305   struct GNUNET_MYSQL_Context *mc;
306
307   mc = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_Context));
308   mc->cfg = cfg;
309   mc->section = section;
310   mc->cnffile = get_my_cnf_path (cfg, section);
311   
312   return mc;
313 }
314
315
316 /**
317  * Close database connection and all prepared statements (we got a DB
318  * error).
319  *
320  * @param mc mysql context
321  */
322 void
323 GNUNET_MYSQL_statements_invalidate (struct GNUNET_MYSQL_Context *mc)
324 {
325   struct GNUNET_MYSQL_StatementHandle *sh;
326
327   for (sh = mc->shead; NULL != sh; sh = sh->next)
328   {
329     if (GNUNET_YES == sh->valid)
330     {
331       mysql_stmt_close (sh->statement);
332       sh->valid = GNUNET_NO;
333     }
334     sh->statement = NULL;
335   }
336   if (NULL != mc->dbf)
337   {
338     mysql_close (mc->dbf);
339     mc->dbf = NULL;
340   }
341 }
342
343
344 /**
345  * Destroy a mysql context.  Also frees all associated prepared statements.
346  * 
347  * @param mc context to destroy
348  */
349 void
350 GNUNET_MYSQL_context_destroy (struct GNUNET_MYSQL_Context *mc)
351 {
352   struct GNUNET_MYSQL_StatementHandle *sh;
353
354   GNUNET_MYSQL_statements_invalidate (mc);
355   while (NULL != (sh = mc->shead))
356   {
357     GNUNET_CONTAINER_DLL_remove (mc->shead, mc->stail, sh);
358     GNUNET_free (sh->query);
359     GNUNET_free (sh);
360   }
361   GNUNET_free (mc);
362   mysql_library_end ();
363 }
364
365
366 /**
367  * Prepare a statement.  Prepared statements are automatically discarded
368  * when the MySQL context is destroyed.
369  *
370  * @param mc mysql context
371  * @param query query text
372  * @return prepared statement, NULL on error
373  */
374 struct GNUNET_MYSQL_StatementHandle *
375 GNUNET_MYSQL_statement_prepare (struct GNUNET_MYSQL_Context *mc,
376                                 const char *query)
377 {
378   struct GNUNET_MYSQL_StatementHandle *sh;
379
380   sh = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_StatementHandle));
381   sh->query = GNUNET_strdup (query);
382   GNUNET_CONTAINER_DLL_insert (mc->shead, mc->stail, sh);
383   return sh;
384 }
385
386
387 /**
388  * Run a SQL statement.
389  *
390  * @param mc mysql context
391  * @param sql SQL statement to run
392  * @return GNUNET_OK on success
393  *         GNUNET_SYSERR if there was a problem
394  */
395 int
396 GNUNET_MYSQL_statement_run (struct GNUNET_MYSQL_Context *mc,
397                             const char *sql)
398 {
399   if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc)))
400     return GNUNET_SYSERR;
401   mysql_query (mc->dbf, sql);
402   if (mysql_error (mc->dbf)[0])
403   {
404     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_query", mc);
405     GNUNET_MYSQL_statements_invalidate (mc);
406     return GNUNET_SYSERR;
407   }
408   return GNUNET_OK;
409 }
410
411
412 /**
413  * Prepare a statement for running.
414  *
415  * @param mc mysql context
416  * @param sh statement handle to prepare
417  * @return GNUNET_OK on success
418  */
419 static int
420 prepare_statement (struct GNUNET_MYSQL_Context *mc,
421                    struct GNUNET_MYSQL_StatementHandle *sh)
422 {
423   if (GNUNET_YES == sh->valid)
424     return GNUNET_OK;
425   if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc)))
426     return GNUNET_SYSERR;
427   sh->statement = mysql_stmt_init (mc->dbf);
428   if (NULL == sh->statement)
429   {
430     GNUNET_MYSQL_statements_invalidate (mc);
431     return GNUNET_SYSERR;
432   }
433   if (0 != mysql_stmt_prepare (sh->statement, sh->query, strlen (sh->query)))
434   {
435     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_stmt_prepare", mc);
436     mysql_stmt_close (sh->statement);
437     sh->statement = NULL;
438     GNUNET_MYSQL_statements_invalidate (mc);
439     return GNUNET_SYSERR;
440   }
441   sh->valid = GNUNET_YES;
442   return GNUNET_OK;
443 }
444
445
446 /**
447  * Get internal handle for a prepared statement.  This function should rarely
448  * be used, and if, with caution!  On failures during the interaction with
449  * the handle, you must call 'GNUNET_MYSQL_statements_invalidate'!
450  *
451  * @param mc mysql context
452  * @param sh prepared statement to introspect
453  * @return MySQL statement handle, NULL on error
454  */
455 MYSQL_STMT *
456 GNUNET_MYSQL_statement_get_stmt (struct GNUNET_MYSQL_Context *mc,
457                                  struct GNUNET_MYSQL_StatementHandle *sh)
458 {
459   (void) prepare_statement (mc, sh);
460   return sh->statement;
461 }
462
463
464 /**
465  * Bind the parameters for the given MySQL statement
466  * and run it.
467  *
468  * @param mc mysql context
469  * @param sh statement to bind and run
470  * @param ap arguments for the binding
471  * @return GNUNET_SYSERR on error, GNUNET_OK on success
472  */
473 static int
474 init_params (struct GNUNET_MYSQL_Context *mc, 
475              struct GNUNET_MYSQL_StatementHandle *sh,
476              va_list ap)
477 {
478   MYSQL_BIND qbind[MAX_PARAM];
479   unsigned int pc;
480   unsigned int off;
481   enum enum_field_types ft;
482
483   pc = mysql_stmt_param_count (sh->statement);
484   if (pc > MAX_PARAM)
485   {
486     /* increase internal constant! */
487     GNUNET_break (0);
488     return GNUNET_SYSERR;
489   }
490   memset (qbind, 0, sizeof (qbind));
491   off = 0;
492   ft = 0;
493   while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))))
494   {
495     qbind[off].buffer_type = ft;
496     switch (ft)
497     {
498     case MYSQL_TYPE_FLOAT:
499       qbind[off].buffer = va_arg (ap, float *);
500
501       break;
502     case MYSQL_TYPE_LONGLONG:
503       qbind[off].buffer = va_arg (ap, unsigned long long *);
504       qbind[off].is_unsigned = va_arg (ap, int);
505
506       break;
507     case MYSQL_TYPE_LONG:
508       qbind[off].buffer = va_arg (ap, unsigned int *);
509       qbind[off].is_unsigned = va_arg (ap, int);
510
511       break;
512     case MYSQL_TYPE_VAR_STRING:
513     case MYSQL_TYPE_STRING:
514     case MYSQL_TYPE_BLOB:
515       qbind[off].buffer = va_arg (ap, void *);
516       qbind[off].buffer_length = va_arg (ap, unsigned long);
517       qbind[off].length = va_arg (ap, unsigned long *);
518
519       break;
520     default:
521       /* unsupported type */
522       GNUNET_break (0);
523       return GNUNET_SYSERR;
524     }
525     pc--;
526     off++;
527   }
528   if (!((pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1)))
529   {
530     GNUNET_break (0);
531     return GNUNET_SYSERR;
532   }
533   if (mysql_stmt_bind_param (sh->statement, qbind))
534   {
535     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
536                      "mysql",
537                      _("`%s' failed at %s:%d with error: %s\n"),
538                      "mysql_stmt_bind_param", __FILE__, __LINE__,
539                      mysql_stmt_error (sh->statement));
540     GNUNET_MYSQL_statements_invalidate (mc);
541     return GNUNET_SYSERR;
542   }
543   if (mysql_stmt_execute (sh->statement))
544   {
545     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
546                      "mysql",
547                      _("`%s' failed at %s:%d with error: %s\n"),
548                      "mysql_stmt_execute", __FILE__, __LINE__,
549                      mysql_stmt_error (sh->statement));
550     GNUNET_MYSQL_statements_invalidate (mc);
551     return GNUNET_SYSERR;
552   }
553   return GNUNET_OK;
554 }
555
556
557
558 /**
559  * Run a prepared SELECT statement.
560  *
561  * @param mc mysql context
562  * @param s statement to run
563  * @param result_size number of elements in results array
564  * @param results pointer to already initialized MYSQL_BIND
565  *        array (of sufficient size) for passing results
566  * @param processor function to call on each result
567  * @param processor_cls extra argument to processor
568  * @param ap pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
569  *        values (size + buffer-reference for pointers); terminated
570  *        with "-1"
571  * @return GNUNET_SYSERR on error, otherwise
572  *         the number of successfully affected (or queried) rows
573  */
574 int
575 GNUNET_MYSQL_statement_run_prepared_select_va (struct GNUNET_MYSQL_Context *mc,
576                                                struct GNUNET_MYSQL_StatementHandle *s,
577                                                unsigned int result_size,
578                                                MYSQL_BIND * results,
579                                                GNUNET_MYSQL_DataProcessor processor,
580                                                void *processor_cls,
581                                                va_list ap)
582 {
583   int ret;
584   unsigned int rsize;
585   int total;
586
587   if (GNUNET_OK != prepare_statement (mc, s))
588   {
589     GNUNET_break (0);
590     return GNUNET_SYSERR;
591   }
592   if (GNUNET_OK != init_params (mc, s, ap))
593   {
594     GNUNET_break (0);
595     return GNUNET_SYSERR;
596   }
597   rsize = mysql_stmt_field_count (s->statement);
598   if (rsize > result_size)
599   {
600     GNUNET_break (0);
601     return GNUNET_SYSERR;
602   }
603   if (mysql_stmt_bind_result (s->statement, results))
604   {
605     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
606                 _("`%s' failed at %s:%d with error: %s\n"),
607                 "mysql_stmt_bind_result", __FILE__, __LINE__,
608                 mysql_stmt_error (s->statement));
609     GNUNET_MYSQL_statements_invalidate (mc);
610     return GNUNET_SYSERR;
611   }
612  
613   total = 0;
614   while (1)
615   {
616     ret = mysql_stmt_fetch (s->statement);
617     if (ret == MYSQL_NO_DATA)
618       break;
619     if (ret != 0)
620     {
621       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
622                        "mysql",
623                        _("`%s' failed at %s:%d with error: %s\n"),
624                        "mysql_stmt_fetch", __FILE__, __LINE__,
625                        mysql_stmt_error (s->statement));
626       GNUNET_MYSQL_statements_invalidate (mc);
627       return GNUNET_SYSERR;
628     }
629     total++;
630     if ( (NULL == processor) ||
631          (GNUNET_OK != processor (processor_cls, rsize, results)) )
632       break;
633   }
634   mysql_stmt_reset (s->statement);
635   return total;
636 }
637
638
639 /**
640  * Run a prepared SELECT statement.
641  *
642  * @param mc mysql context
643  * @param sh handle to SELECT statment
644  * @param result_size number of elements in results array
645  * @param results pointer to already initialized MYSQL_BIND
646  *        array (of sufficient size) for passing results
647  * @param processor function to call on each result
648  * @param processor_cls extra argument to processor
649  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
650  *        values (size + buffer-reference for pointers); terminated
651  *        with "-1"
652  * @return GNUNET_SYSERR on error, otherwise
653  *         the number of successfully affected (or queried) rows
654  */
655 int
656 GNUNET_MYSQL_statement_run_prepared_select (struct GNUNET_MYSQL_Context *mc,
657                                             struct GNUNET_MYSQL_StatementHandle *sh,
658                                             unsigned int result_size, MYSQL_BIND * results,
659                                             GNUNET_MYSQL_DataProcessor processor,
660                                             void *processor_cls, ...)
661 {
662   va_list ap;
663   int ret;
664
665   va_start (ap, processor_cls);
666   ret = GNUNET_MYSQL_statement_run_prepared_select_va (mc, sh, result_size, results, processor, processor_cls, ap);
667   va_end (ap);
668   return ret;
669 }
670
671
672 /**
673  * Run a prepared statement that does NOT produce results.
674  *
675  * @param mc mysql context
676  * @param sh handle to statment
677  * @param insert_id NULL or address where to store the row ID of whatever
678  *        was inserted (only for INSERT statements!)
679  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
680  *        values (size + buffer-reference for pointers); terminated
681  *        with "-1"
682  * @return GNUNET_SYSERR on error, otherwise
683  *         the number of successfully affected rows
684  */
685 int
686 GNUNET_MYSQL_statement_run_prepared (struct GNUNET_MYSQL_Context *mc,
687                                      struct GNUNET_MYSQL_StatementHandle *sh,
688                                      unsigned long long *insert_id, ...)
689 {
690   va_list ap;
691   int affected;
692
693   if (GNUNET_OK != prepare_statement (mc, sh))
694     return GNUNET_SYSERR;
695   va_start (ap, insert_id);
696   if (GNUNET_OK != init_params (mc, sh, ap))
697   {
698     va_end (ap);
699     return GNUNET_SYSERR;
700   }
701   va_end (ap);
702   affected = mysql_stmt_affected_rows (sh->statement);
703   if (NULL != insert_id)
704     *insert_id = (unsigned long long) mysql_stmt_insert_id (sh->statement);
705   mysql_stmt_reset (sh->statement);
706   return affected;
707 }
708
709
710 /* end of mysql.c */
711