7872568b290a728cbde69e308a68ab1952b22229
[sip-router] / src / modules / db_mysql / my_cmd.c
1 /* 
2  * Copyright (C) 2001-2003 FhG Fokus
3  * Copyright (C) 2006-2007 iptelorg GmbH
4  *
5  * This file is part of Kamailio, a free SIP server.
6  *
7  * Kamailio is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version
11  *
12  * Kamailio is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License 
18  * along with this program; if not, write to the Free Software 
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 /** @addtogroup mysql
23  *  @{
24  */
25
26 /* the following macro will break the compile on solaris */
27 #if !defined (__SVR4) && !defined (__sun)
28    #define _XOPEN_SOURCE 4     /* bsd */
29 #endif
30 #define _XOPEN_SOURCE_EXTENDED 1    /* solaris */
31 #define _SVID_SOURCE 1 /* timegm */
32 #define _DEFAULT_SOURCE 1 /* _SVID_SOURCE is deprecated */
33
34 #include "my_cmd.h"
35
36 #include "my_con.h"
37 #include "db_mysql.h"
38 #include "my_fld.h"
39
40 #include "../../core/mem/mem.h"
41 #include "../../core/str.h"
42 #include "../../lib/srdb2/db_cmd.h"
43 #include "../../core/ut.h"
44 #include "../../core/dprint.h"
45
46 #include <strings.h>
47 #include <stdio.h>
48 #include <time.h>  /*strptime, XOPEN issue must be >=4 */
49 #include <string.h>
50 #include <errmsg.h>
51 #include <mysqld_error.h>
52
53 #define STR_BUF_SIZE 1024
54
55 #ifdef MYSQL_FAKE_NULL
56
57 #define FAKE_NULL_STRING "[~NULL~]"
58 static str  FAKE_NULL_STR = STR_STATIC_INIT(FAKE_NULL_STRING);
59
60 /* avoid warning: this decimal constant is unsigned only in ISO C90 :-) */
61 #define FAKE_NULL_INT (-2147483647 - 1)
62 #endif
63
64 enum {
65         STR_DELETE,
66         STR_INSERT,
67         STR_UPDATE,
68         STR_SELECT,
69         STR_REPLACE,
70         STR_SET,
71         STR_WHERE,
72         STR_IS,
73         STR_AND,
74         STR_OR,
75         STR_ESC,
76         STR_OP_EQ,
77         STR_OP_NE,
78         STR_OP_LT,
79         STR_OP_GT,
80         STR_OP_LEQ,
81         STR_OP_GEQ,
82         STR_VALUES,
83         STR_FROM
84 };
85
86 static str strings[] = {
87         STR_STATIC_INIT("delete from "),
88         STR_STATIC_INIT("insert into "),
89         STR_STATIC_INIT("update "),
90         STR_STATIC_INIT("select "),
91         STR_STATIC_INIT("replace "),
92         STR_STATIC_INIT(" set "),
93         STR_STATIC_INIT(" where "),
94         STR_STATIC_INIT(" is "),
95         STR_STATIC_INIT(" and "),
96         STR_STATIC_INIT(" or "),
97         STR_STATIC_INIT("?"),
98         STR_STATIC_INIT("="),
99         STR_STATIC_INIT("!="),
100         STR_STATIC_INIT("<"),
101         STR_STATIC_INIT(">"),
102         STR_STATIC_INIT("<="),
103         STR_STATIC_INIT(">="),
104         STR_STATIC_INIT(") values ("),
105         STR_STATIC_INIT(" from ")
106 };
107
108
109 #define APPEND_STR(p, str) do {          \
110         memcpy((p), (str).s, (str).len); \
111         (p) += (str).len;                                \
112 } while(0)
113
114
115 #define APPEND_CSTR(p, cstr) do { \
116     int _len = strlen(cstr);      \
117         memcpy((p), (cstr), _len);        \
118         (p) += _len;                              \
119 } while(0)
120
121
122 static int upload_cmd(db_cmd_t* cmd);
123
124
125 static void my_cmd_free(db_cmd_t* cmd, struct my_cmd* payload)
126 {
127         db_drv_free(&payload->gen);
128         if (payload->sql_cmd.s) pkg_free(payload->sql_cmd.s);
129         if (payload->st) mysql_stmt_close(payload->st);
130         pkg_free(payload);
131 }
132
133
134 /** Builds a DELETE SQL statement.The function builds DELETE statement where
135  * cmd->match specify WHERE clause.  
136  * @param sql_cmd SQL statement as a result of this function 
137  * @param cmd input for statement creation
138  * @return -1 on error, 0 on success
139  */
140 static int build_delete_cmd(str* sql_cmd, db_cmd_t* cmd)
141 {
142         db_fld_t* fld;
143         int i;
144         char* p;
145
146         sql_cmd->len = strings[STR_DELETE].len;
147         sql_cmd->len += cmd->table.len;
148
149         if (!DB_FLD_EMPTY(cmd->match)) {
150                 sql_cmd->len += strings[STR_WHERE].len;
151
152                 for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
153                         sql_cmd->len += strlen(fld[i].name);
154
155                         switch(fld[i].op) {
156                         case DB_EQ:  sql_cmd->len += strings[STR_OP_EQ].len; break;
157                         case DB_NE:  sql_cmd->len += strings[STR_OP_NE].len; break;
158                         case DB_LT:  sql_cmd->len += strings[STR_OP_LT].len; break;
159                         case DB_GT:  sql_cmd->len += strings[STR_OP_GT].len; break;
160                         case DB_LEQ: sql_cmd->len += strings[STR_OP_LEQ].len; break;
161                         case DB_GEQ: sql_cmd->len += strings[STR_OP_GEQ].len; break;
162                         default:
163                                 ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
164                                 return -1;
165                         }
166
167                         sql_cmd->len += strings[STR_ESC].len;
168                         
169                         if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
170                 }
171         }
172
173         sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
174         if (sql_cmd->s == NULL) {
175                 ERR("mysql: No memory left\n");
176                 return -1;
177         }
178         p = sql_cmd->s;
179         
180         APPEND_STR(p, strings[STR_DELETE]);
181         APPEND_STR(p, cmd->table);
182
183         if (!DB_FLD_EMPTY(cmd->match)) {
184                 APPEND_STR(p, strings[STR_WHERE]);
185
186                 for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
187                         APPEND_CSTR(p, fld[i].name);
188
189                         switch(fld[i].op) {
190                         case DB_EQ:  APPEND_STR(p, strings[STR_OP_EQ]);  break;
191                         case DB_NE:  APPEND_STR(p, strings[STR_OP_NE]);  break;
192                         case DB_LT:  APPEND_STR(p, strings[STR_OP_LT]);  break;
193                         case DB_GT:  APPEND_STR(p, strings[STR_OP_GT]);  break;
194                         case DB_LEQ: APPEND_STR(p, strings[STR_OP_LEQ]); break;
195                         case DB_GEQ: APPEND_STR(p, strings[STR_OP_GEQ]); break;
196                         }
197                         
198                         APPEND_STR(p, strings[STR_ESC]);
199                         if (!DB_FLD_LAST(fld[i + 1])) APPEND_STR(p, strings[STR_AND]);
200                 }
201         }
202                         
203         *p = '\0';
204         return 0;
205 }
206
207
208 /**
209  *  Builds SELECT statement where cmd->values specify column names
210  *  and cmd->match specify WHERE clause.
211  * @param sql_cmd SQL statement as a result of this function
212  * @param cmd     input for statement creation
213  */
214 static int build_select_cmd(str* sql_cmd, db_cmd_t* cmd)
215 {
216         db_fld_t* fld;
217         int i;
218         char* p;
219
220         sql_cmd->len = strings[STR_SELECT].len;
221
222         if (DB_FLD_EMPTY(cmd->result)) {
223                 sql_cmd->len += 1; /* "*" */
224         } else {
225                 for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
226                         sql_cmd->len += strlen(fld[i].name);
227                         if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += 1; /* , */
228                 }
229         }
230         sql_cmd->len += strings[STR_FROM].len;
231         sql_cmd->len += cmd->table.len;
232
233         if (!DB_FLD_EMPTY(cmd->match)) {
234                 sql_cmd->len += strings[STR_WHERE].len;
235
236                 for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
237                         sql_cmd->len += strlen(fld[i].name);
238
239                         switch(fld[i].op) {
240                         case DB_EQ:  sql_cmd->len += strings[STR_OP_EQ].len; break;
241                         case DB_NE:  sql_cmd->len += strings[STR_OP_NE].len; break;
242                         case DB_LT:  sql_cmd->len += strings[STR_OP_LT].len; break;
243                         case DB_GT:  sql_cmd->len += strings[STR_OP_GT].len; break;
244                         case DB_LEQ: sql_cmd->len += strings[STR_OP_LEQ].len; break;
245                         case DB_GEQ: sql_cmd->len += strings[STR_OP_GEQ].len; break;
246                         default:
247                                 ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
248                                 return -1;
249                         }
250
251                         sql_cmd->len += strings[STR_ESC].len;
252                         
253                         if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
254                 }
255         }
256
257         sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
258         if (sql_cmd->s == NULL) {
259                 ERR("mysql: No memory left\n");
260                 return -1;
261         }
262         p = sql_cmd->s;
263         
264         APPEND_STR(p, strings[STR_SELECT]);
265         if (DB_FLD_EMPTY(cmd->result)) {
266                 *p++ = '*';
267         } else {
268                 for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
269                         APPEND_CSTR(p, fld[i].name);
270                         if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
271                 }
272         }
273         APPEND_STR(p, strings[STR_FROM]);
274         APPEND_STR(p, cmd->table);
275
276         if (!DB_FLD_EMPTY(cmd->match)) {
277                 APPEND_STR(p, strings[STR_WHERE]);
278
279                 for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
280                         APPEND_CSTR(p, fld[i].name);
281
282                         switch(fld[i].op) {
283                         case DB_EQ:  APPEND_STR(p, strings[STR_OP_EQ]);  break;
284                         case DB_NE:  APPEND_STR(p, strings[STR_OP_NE]);  break;
285                         case DB_LT:  APPEND_STR(p, strings[STR_OP_LT]);  break;
286                         case DB_GT:  APPEND_STR(p, strings[STR_OP_GT]);  break;
287                         case DB_LEQ: APPEND_STR(p, strings[STR_OP_LEQ]); break;
288                         case DB_GEQ: APPEND_STR(p, strings[STR_OP_GEQ]); break;
289                         }
290                         
291                         APPEND_STR(p, strings[STR_ESC]);
292                         if (!DB_FLD_LAST(fld[i + 1])) APPEND_STR(p, strings[STR_AND]);
293                 }
294         }
295
296         *p = '\0';
297         return 0;
298 }
299
300
301 /**
302  *  Builds REPLACE statement where cmd->values specify column names.
303  * @param sql_cmd SQL statement as a result of this function
304  * @param cmd     input for statement creation
305  */
306 static int build_replace_cmd(str* sql_cmd, db_cmd_t* cmd)
307 {
308         db_fld_t* fld;
309         int i;
310         char* p;
311
312         sql_cmd->len = strings[STR_REPLACE].len;
313         sql_cmd->len += cmd->table.len;
314         sql_cmd->len += 2; /* " (" */
315
316         for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
317                 sql_cmd->len += strlen(fld[i].name);
318                 sql_cmd->len += strings[STR_ESC].len;
319                 if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += 2; /* , twice */
320         }
321         sql_cmd->len += strings[STR_VALUES].len;
322     sql_cmd->len += 1; /* ) */
323
324         sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
325         if (sql_cmd->s == NULL) {
326                 ERR("mysql: No memory left\n");
327                 return -1;
328         }
329         p = sql_cmd->s;
330         
331         APPEND_STR(p, strings[STR_REPLACE]);
332         APPEND_STR(p, cmd->table);
333         *p++ = ' ';
334         *p++ = '(';
335
336         for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
337                 APPEND_CSTR(p, fld[i].name);
338                 if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
339         }
340         APPEND_STR(p, strings[STR_VALUES]);
341
342         for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
343                 APPEND_STR(p, strings[STR_ESC]);
344                 if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
345         }
346         *p++ = ')';
347         *p = '\0';
348         return 0;
349 }
350
351
352 /**
353  *  Reallocatable string buffer.
354  */
355 struct string_buffer {
356         char *s;                        /**< allocated memory itself */
357         int   len;                      /**< used memory */
358         int   size;                     /**< total size of allocated memory */
359         int   increment;        /**< increment when realloc is necessary */ 
360 };
361
362
363 /**
364  *  Add new string into string buffer.
365  * @param sb    string buffer
366  * @param nstr  string to add
367  * @return      0 if OK, -1 if failed
368  */
369 static inline int sb_add(struct string_buffer *sb, str *nstr)
370 {
371         int new_size = 0;
372         int rsize = sb->len + nstr->len;
373         int asize;
374         char *newp;
375         
376         if ( rsize > sb->size ) {
377                 asize = rsize - sb->size;
378                 new_size = sb->size + (asize / sb->increment  + (asize % sb->increment > 0)) * sb->increment;
379                 newp = pkg_malloc(new_size);
380                 if (!newp) {
381                         ERR("mysql: No memory left\n");
382                         return -1;
383                 }
384                 if (sb->s) {
385                         memcpy(newp, sb->s, sb->len);
386                         pkg_free(sb->s);
387                 }
388                 sb->s = newp;
389                 sb->size = new_size;
390         }
391         memcpy(sb->s + sb->len, nstr->s, nstr->len);
392         sb->len += nstr->len;
393         return 0;
394 }
395
396
397 /**
398  *  Set members of str variable.
399  *  Used for temporary str variables. 
400  */
401 static inline str* set_str(str *str, const char *s)
402 {
403         str->s = (char *)s;
404         str->len = strlen(s);
405         return str;
406 }
407
408
409 /**
410  *  Builds UPDATE statement where cmd->valss specify column name-value pairs
411  *  and cmd->match specify WHERE clause.
412  * @param sql_cmd  SQL statement as a result of this function
413  * @param cmd      input for statement creation
414  */
415 static int build_update_cmd(str* sql_cmd, db_cmd_t* cmd)
416 {
417         struct string_buffer sql_buf = {.s = NULL, .len = 0, .size = 0, .increment = 128};
418         db_fld_t* fld;
419         int i;
420         int rv = 0;
421         str tmpstr;
422
423         rv = sb_add(&sql_buf, &strings[STR_UPDATE]);    /* "UPDATE " */
424         rv |= sb_add(&sql_buf, &cmd->table);                    /* table name */
425         rv |= sb_add(&sql_buf, &strings[STR_SET]);              /* " SET " */
426
427         /* column name-value pairs */
428         for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
429                 rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
430                 rv |= sb_add(&sql_buf, set_str(&tmpstr, " = "));
431                 rv |= sb_add(&sql_buf, &strings[STR_ESC]);
432                 if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ", "));
433         }
434         if (rv) {
435                 goto err;
436         }
437
438         if (!DB_FLD_EMPTY(cmd->match)) {
439                 rv |= sb_add(&sql_buf, &strings[STR_WHERE]);
440
441                 for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
442                         rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
443
444                         switch(fld[i].op) {
445                         case DB_EQ:  rv |= sb_add(&sql_buf, &strings[STR_OP_EQ]);  break;
446                         case DB_NE:  rv |= sb_add(&sql_buf, &strings[STR_OP_NE]);  break;
447                         case DB_LT:  rv |= sb_add(&sql_buf, &strings[STR_OP_LT]);  break;
448                         case DB_GT:  rv |= sb_add(&sql_buf, &strings[STR_OP_GT]);  break;
449                         case DB_LEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_LEQ]); break;
450                         case DB_GEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_GEQ]); break;
451                         }
452                         
453                         rv |= sb_add(&sql_buf, &strings[STR_ESC]);
454                         if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, &strings[STR_AND]);
455                 }
456         }
457         rv |= sb_add(&sql_buf, set_str(&tmpstr, "\0"));
458         if (rv) {
459                 goto err;
460         }
461         sql_cmd->s = sql_buf.s;
462         sql_cmd->len = sql_buf.len;
463         return 0;
464
465 err:
466         if (sql_buf.s) pkg_free(sql_buf.s);
467         return -1;
468 }
469
470
471 static inline void update_field(MYSQL_BIND *param, db_fld_t* fld)
472 {
473         struct my_fld* fp;      /* field payload */
474         struct tm* t;
475         
476         fp = DB_GET_PAYLOAD(fld);
477
478 #ifndef MYSQL_FAKE_NULL
479         fp->is_null = fld->flags & DB_NULL;
480         if (fp->is_null) return;
481 #else
482         if (fld->flags & DB_NULL) {
483                 switch(fld->type) {
484                 case DB_STR:
485                 case DB_CSTR:
486                         param->buffer = FAKE_NULL_STR.s;
487                         fp->length = FAKE_NULL_STR.len;
488                         break;
489                 case DB_INT:
490                         *(int*)param->buffer = FAKE_NULL_INT;
491                         break;
492                 case DB_BLOB:
493                 case DB_DATETIME:
494                 case DB_NONE:
495                 case DB_FLOAT:
496                 case DB_DOUBLE:
497                 case DB_BITMAP:
498                         /* we don't have fake null value for these types */
499                         fp->is_null = DB_NULL;
500                         break;
501                 }
502                 return;
503         }
504 #endif
505         switch(fld->type) {
506         case DB_STR:
507                 param->buffer = fld->v.lstr.s;
508                 fp->length = fld->v.lstr.len;
509                 break;
510
511         case DB_BLOB:
512                 param->buffer = fld->v.blob.s;
513                 fp->length = fld->v.blob.len;
514                 break;
515
516         case DB_CSTR:
517                 param->buffer = (char*)fld->v.cstr;
518                 fp->length = strlen(fld->v.cstr);
519                 break;
520
521         case DB_DATETIME:
522                 t = gmtime(&fld->v.time);
523                 fp->time.second = t->tm_sec;
524                 fp->time.minute = t->tm_min;
525                 fp->time.hour = t->tm_hour;
526                 fp->time.day = t->tm_mday;
527                 fp->time.month = t->tm_mon + 1;
528                 fp->time.year = t->tm_year + 1900;
529                 break;
530                 
531         case DB_NONE:
532         case DB_INT:
533         case DB_FLOAT:
534         case DB_DOUBLE:
535         case DB_BITMAP:
536                 /* No need to do anything for these types */
537                 break;
538
539         }
540 }
541
542
543 /**
544  * Update values of MySQL bound parameters with values from
545  * the DB API.
546  * @param cmd Command structure which contains pointers to MYSQL_STMT and parameters values
547  * @see bind_mysql_params
548  */
549 static inline void set_mysql_params(db_cmd_t* cmd)
550 {
551         struct my_cmd* mcmd;
552         int i;
553
554         mcmd = DB_GET_PAYLOAD(cmd);
555
556         /* FIXME: We are updating internals of the prepared statement here,
557          * this is probably not nice but I could not find another way of
558          * updating the pointer to the buffer without the need to run
559          * mysql_stmt_bind_param again (which would be innefficient)
560          */
561         for(i = 0; i < cmd->vals_count; i++) {
562                 update_field(mcmd->st->params + i, cmd->vals + i);
563         }
564
565         for(i = 0; i < cmd->match_count; i++) {
566                 update_field(mcmd->st->params + cmd->vals_count + i, cmd->match + i);
567         }
568 }
569
570
571 static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
572 {
573         int i;
574         struct my_fld* rp; /* Payload of the current field in result */
575         struct tm t;
576
577         /* Iterate through all the fields returned by MySQL and convert
578          * them to DB API representation if necessary
579          */
580
581         for(i = 0; i < st->field_count; i++) {
582                 rp = DB_GET_PAYLOAD(result + i);
583
584                 if (rp->is_null) {
585                         result[i].flags |= DB_NULL;
586                         continue;
587                 } else {
588                         result[i].flags &= ~DB_NULL;
589                 }
590
591                 switch(result[i].type) {
592                 case DB_STR:
593                         result[i].v.lstr.len = rp->length;
594 #ifdef MYSQL_FAKE_NULL
595                         if (STR_EQ(FAKE_NULL_STR,result[i].v.lstr)) {
596                                 result[i].flags |= DB_NULL;
597                         }
598 #endif
599                         break;
600
601                 case DB_BLOB:
602                         result[i].v.blob.len = rp->length;
603                         break;
604
605                 case DB_CSTR:
606                         if (rp->length < STR_BUF_SIZE) {
607                                 result[i].v.cstr[rp->length] = '\0';
608                         } else {
609                                 /* Truncated field but rp->length contains full size,
610                                  * zero terminated the last byte in the buffer
611                                  */
612                                 result[i].v.cstr[STR_BUF_SIZE - 1] = '\0';
613                         }
614 #ifdef MYSQL_FAKE_NULL
615                         if (strcmp(FAKE_NULL_STR.s,result[i].v.cstr)==0) {
616                                 result[i].flags |= DB_NULL;
617                         }
618 #endif
619                         break;
620                         
621                 case DB_DATETIME:
622                         memset(&t, '\0', sizeof(struct tm));
623                         t.tm_sec = rp->time.second;
624                         t.tm_min = rp->time.minute;
625                         t.tm_hour = rp->time.hour;
626                         t.tm_mday = rp->time.day;
627                         t.tm_mon = rp->time.month - 1;
628                         t.tm_year = rp->time.year - 1900;
629
630                         /* Daylight saving information got lost in the database
631                          * so let timegm to guess it. This eliminates the bug when
632                          * contacts reloaded from the database have different time
633                          * of expiration by one hour when daylight saving is used
634                          */ 
635                         t.tm_isdst = -1;
636 #ifdef HAVE_TIMEGM
637                         result[i].v.time = timegm(&t);
638 #else
639                         result[i].v.time = _timegm(&t);
640 #endif /* HAVE_TIMEGM */
641                         break;
642
643                 case DB_INT:
644 #ifdef MYSQL_FAKE_NULL
645                         if (FAKE_NULL_INT==result[i].v.int4) {
646                                 result[i].flags |= DB_NULL;
647                         }
648                         break;
649 #endif
650                 case DB_NONE:
651                 case DB_FLOAT:
652                 case DB_DOUBLE:
653                 case DB_BITMAP:
654                         /* No need to do anything for these types */
655                         break;
656                 }
657         }
658         
659         return 0;
660 }
661
662
663 /**
664  * This is the main command execution function. The function contains
665  * all the necessary logic to detect reset or disconnected database
666  * connections and uploads commands to the server if necessary.
667  * @param cmd Command to be executed
668  * @return    0 if OK, <0 on MySQL failure, >0 on DB API failure
669  */
670 static int exec_cmd_safe(db_cmd_t* cmd)
671 {
672     int i, err;
673     db_con_t* con;
674     struct my_cmd* mcmd;
675     struct my_con* mcon;
676         
677     /* First things first: retrieve connection info
678      * from the currently active connection and also
679      * mysql payload from the database command
680      */
681     mcmd = DB_GET_PAYLOAD(cmd);
682     con = cmd->ctx->con[db_payload_idx];
683     mcon = DB_GET_PAYLOAD(con);
684     
685     for(i = 0; i <= my_retries; i++) {
686         if ((mcon->flags & MY_CONNECTED) == 0) {
687             /* The connection is disconnected, try to reconnect */
688             if (my_con_connect(con)) {
689                 INFO("mysql: exec_cmd_safe failed to re-connect\n");
690                 continue;
691             }
692         }       
693         
694         /* Next check the number of resets in the database connection, if this
695          * number is higher than the number we keep in my_cmd structure in
696          * last_reset variable then the connection was reset and we need to
697          * upload the command again to the server before executing it, because
698          * the server recycles all server side information upon disconnect.
699          */
700         if (mcon->resets > mcmd->last_reset) {
701             INFO("mysql: Connection reset detected, uploading command to server\n");
702             err = upload_cmd(cmd);
703             if (err < 0) {
704                 INFO("mysql: Error while uploading command\n");
705                 continue;
706             } else if (err > 0) {
707                 /* DB API error, this is a serious problem such as memory
708                  * allocation failure, bail out
709                  */
710                 return 1;
711             }
712         }
713         
714         set_mysql_params(cmd);
715         err = mysql_stmt_execute(mcmd->st);
716         if (err == 0) {
717             /* The command was executed successfully, now fetch all data to
718              * the client if it was requested by the user */
719             if (mcmd->flags & MY_FETCH_ALL) {
720                 err = mysql_stmt_store_result(mcmd->st);
721                 if (err) {
722                     INFO("mysql: Error while fetching data to client.\n");
723                     goto error;
724                 }
725             }
726             return 0;
727         }
728         
729     error:
730         /* Command execution failed, log a message and try to reconnect */
731         INFO("mysql: libmysql: %d, %s\n", mysql_stmt_errno(mcmd->st),
732              mysql_stmt_error(mcmd->st));
733         INFO("mysql: Error while executing command on server, trying to reconnect\n");
734
735         my_con_disconnect(con);
736         if (my_con_connect(con)) {
737             INFO("mysql: Failed to reconnect server\n");
738         } else {
739             INFO("mysql: Successfully reconnected server\n");
740         }
741     }
742     
743     INFO("mysql: Failed to execute command, giving up\n");
744     return -1;
745 }
746
747
748 int my_cmd_exec(db_res_t* res, db_cmd_t* cmd)
749 {
750         struct my_cmd* mcmd;
751
752         mcmd = DB_GET_PAYLOAD(cmd);
753
754         mcmd->next_flag = -1;
755         return exec_cmd_safe(cmd);
756 }
757
758
759 /**
760  * Set MYSQL_BIND item.
761  * @param bind destination
762  * @param fld  source
763  */
764 static void set_field(MYSQL_BIND *bind, db_fld_t* fld)
765 {
766         struct my_fld* f;
767         
768         f = DB_GET_PAYLOAD(fld);
769         bind->is_null = &f->is_null;
770         /* We can do it for all the types here, mysql will ignore it
771          * for fixed-size types such as MYSQL_TYPE_LONG
772          */
773         bind->length = &f->length;
774         switch(fld->type) {
775         case DB_INT:
776         case DB_BITMAP:
777                 bind->buffer_type = MYSQL_TYPE_LONG;
778                 bind->buffer = &fld->v.int4;
779                 break;
780         
781         case DB_FLOAT:
782                 bind->buffer_type = MYSQL_TYPE_FLOAT;
783                 bind->buffer = &fld->v.flt;
784                 break;
785                 
786         case DB_DOUBLE:
787                 bind->buffer_type = MYSQL_TYPE_DOUBLE;
788                 bind->buffer = &fld->v.dbl;
789                 break;
790         
791         case DB_DATETIME:
792                 bind->buffer_type = MYSQL_TYPE_DATETIME;
793                 bind->buffer = &f->time;
794                 break;
795         
796         case DB_STR:
797         case DB_CSTR:
798                 bind->buffer_type = MYSQL_TYPE_VAR_STRING;
799                 bind->buffer = ""; /* Updated on runtime */
800                 break;
801         
802         case DB_BLOB:
803                 bind->buffer_type = MYSQL_TYPE_BLOB;
804                 bind->buffer = ""; /* Updated on runtime */
805                 break;
806         
807         case DB_NONE:
808                 /* Eliminates gcc warning */
809                 break;
810         
811         }
812 }
813
814
815 /**
816  * Bind params, give real values into prepared statement.
817  * Up to two sets of parameters are provided.
818  * Both of them are used in UPDATE command, params1 as colspecs and values and
819  * params2 as WHERE clause. In other cases one set could be enough because values
820  * or match (WHERE clause) is needed.
821  * @param st MySQL command statement
822  * @param params1 first set of params
823  * @param params2 second set of params
824  * @return 0 if OK, <0 on MySQL error, >0 on DB API error
825  * @see update_params
826  */
827 static int bind_mysql_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* params2)
828 {
829         int my_idx, fld_idx;
830         int count1, count2;
831         MYSQL_BIND* my_params;
832         int err = 0;
833
834         /* Calculate the number of parameters */
835         for(count1 = 0; !DB_FLD_EMPTY(params1) && !DB_FLD_LAST(params1[count1]); count1++);
836         for(count2 = 0; !DB_FLD_EMPTY(params2) && !DB_FLD_LAST(params2[count2]); count2++);
837         if (st->param_count != count1 + count2) {
838                 BUG("mysql: Number of parameters in SQL command does not match number of DB API parameters\n");
839                 return 1;
840         }
841         
842         my_params = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * (count1 + count2));
843         if (my_params == NULL) {
844                 ERR("mysql: No memory left\n");
845                 return -1;
846         }
847         memset(my_params, '\0', sizeof(MYSQL_BIND) * (count1 + count2));
848
849         /* params1 */
850         my_idx = 0;
851         for (fld_idx = 0; fld_idx < count1; fld_idx++, my_idx++) {
852                 set_field(&my_params[my_idx], params1 + fld_idx);
853         }
854         /* params2 */
855         for (fld_idx = 0; fld_idx < count2; fld_idx++, my_idx++) {
856                 set_field(&my_params[my_idx], params2 + fld_idx);
857         }
858
859         err = mysql_stmt_bind_param(st, my_params);
860         if (err) {
861                 ERR("mysql: libmysqlclient: %d, %s\n", 
862                         mysql_stmt_errno(st), mysql_stmt_error(st));
863                 goto error;
864         }
865
866         /* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
867          * creates a copy in the statement and we will update it there
868          */
869         pkg_free(my_params);
870         return err;
871    
872  error:
873         if (my_params) pkg_free(my_params);
874         return err;
875 }
876
877
878 /*
879  * FIXME: This function will only work if we have one db connection
880  * in every context, otherwise it would initialize the result set
881  * from the first connection in the context.
882  */
883 static int check_result(db_cmd_t* cmd, struct my_cmd* payload)
884 {
885         int i, n;
886         MYSQL_FIELD *fld;
887         MYSQL_RES *meta = NULL;
888
889         meta = mysql_stmt_result_metadata(payload->st);
890         if (meta == NULL) {
891                 /* No error means no result set to be checked */
892                 if (mysql_stmt_errno(payload->st) == 0) return 0;
893                 ERR("mysql: Error while getting metadata of SQL command: %d, %s\n",
894                         mysql_stmt_errno(payload->st), mysql_stmt_error(payload->st));
895                 return -1;
896         }
897         n = mysql_num_fields(meta);
898         if (cmd->result == NULL) {
899                 /* The result set parameter of db_cmd function was empty, that
900                  * means the command is select * and we have to create the array
901                  * of result fields in the cmd structure manually.
902                  */
903                 cmd->result = db_fld(n + 1);
904                 cmd->result_count = n;
905                 for(i = 0; i < cmd->result_count; i++) {
906                         struct my_fld *f;
907                         if (my_fld(cmd->result + i, cmd->table.s) < 0) goto error;
908                         f = DB_GET_PAYLOAD(cmd->result + i);
909                         fld = mysql_fetch_field_direct(meta, i);
910                         f->name = pkg_malloc(strlen(fld->name)+1);
911                         if (f->name == NULL) {
912                                 ERR("mysql: Out of private memory\n");
913                                 goto error;
914                         }
915                         strcpy(f->name, fld->name);
916                         cmd->result[i].name = f->name;
917                 }
918         } else {
919                 if (cmd->result_count != n) {
920                         BUG("mysql: Number of fields in MySQL result does not match number of parameters in DB API\n");
921                         goto error;
922                 }
923         }
924
925         /* Now iterate through all the columns in the result set and replace
926          * any occurrence of DB_UNKNOWN type with the type of the column
927          * retrieved from the database and if no column name was provided then
928          * update it from the database as well. 
929          */
930         for(i = 0; i < cmd->result_count; i++) {
931                 fld = mysql_fetch_field_direct(meta, i);
932                 if (cmd->result[i].type != DB_NONE) continue;
933                 switch(fld->type) {
934                 case MYSQL_TYPE_TINY:
935                 case MYSQL_TYPE_SHORT:
936                 case MYSQL_TYPE_INT24:
937                 case MYSQL_TYPE_LONG:
938                         cmd->result[i].type = DB_INT;
939                         break;
940
941                 case MYSQL_TYPE_FLOAT:
942                         cmd->result[i].type = DB_FLOAT;
943                         break;
944
945                 case MYSQL_TYPE_DOUBLE:
946                         cmd->result[i].type = DB_DOUBLE;
947                         break;
948
949                 case MYSQL_TYPE_TIMESTAMP:
950                 case MYSQL_TYPE_DATETIME:
951                         cmd->result[i].type = DB_DATETIME;
952                         break;
953
954                 case MYSQL_TYPE_STRING:
955                 case MYSQL_TYPE_VAR_STRING:
956                         cmd->result[i].type = DB_STR;
957                         break;
958
959                 default:
960                         ERR("mysql: Unsupported MySQL column type: %d, table: %s, column: %s\n",
961                                 fld->type, cmd->table.s, fld->name);
962                         goto error;
963                 }
964         }
965         
966         if (meta) mysql_free_result(meta);
967         return 0;
968
969 error:
970         if (meta) mysql_free_result(meta);
971         return 1;
972 }
973
974
975 /* FIXME: Add support for DB_NONE, in this case the function should determine
976  * the type of the column in the database and set the field type appropriately.
977  * This function must be called after check_result.
978  */
979 static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
980 {
981         int i, n, err = 0;
982         struct my_fld* f;
983         MYSQL_BIND* result;
984
985         /* Calculate the number of fields in the result */
986         for(n = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[n]); n++);
987         /* Return immediately if there are no fields in the result set */
988         if (n == 0) return 0;
989
990         result = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * n);
991         if (result == NULL) {
992                 ERR("mysql: No memory left\n");
993                 return 1;
994         }
995         memset(result, '\0', sizeof(MYSQL_BIND) * n);
996         
997         for(i = 0; i < n; i++) {
998                 f = DB_GET_PAYLOAD(fld + i);
999                 result[i].is_null = &f->is_null;
1000                 /* We can do it for all the types here, mysql will ignore it
1001                  * for fixed-size types such as MYSQL_TYPE_LONG
1002                  */
1003                 result[i].length = &f->length;
1004                 switch(fld[i].type) {
1005                 case DB_INT:
1006                 case DB_BITMAP:
1007                         result[i].buffer_type = MYSQL_TYPE_LONG;
1008                         result[i].buffer = &fld[i].v.int4;
1009                         break;
1010
1011                 case DB_FLOAT:
1012                         result[i].buffer_type = MYSQL_TYPE_FLOAT;
1013                         result[i].buffer = &fld[i].v.flt;
1014                         break;
1015                         
1016                 case DB_DOUBLE:
1017                         result[i].buffer_type = MYSQL_TYPE_DOUBLE;
1018                         result[i].buffer = &fld[i].v.dbl;
1019                         break;
1020
1021                 case DB_DATETIME:
1022                         result[i].buffer_type = MYSQL_TYPE_DATETIME;
1023                         result[i].buffer = &f->time;
1024                         break;
1025
1026                 case DB_STR:
1027                         result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
1028                         if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
1029                         if (f->buf.s == NULL) {
1030                                 ERR("mysql: No memory left\n");
1031                                 err = 1;
1032                                 goto error;
1033                         }
1034                         result[i].buffer = f->buf.s;
1035                         fld[i].v.lstr.s = f->buf.s;
1036                         result[i].buffer_length = STR_BUF_SIZE - 1;
1037                         break;
1038
1039                 case DB_CSTR:
1040                         result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
1041                         if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
1042                         if (f->buf.s == NULL) {
1043                                 ERR("mysql: No memory left\n");
1044                                 err = 1;
1045                                 goto error;
1046                         }
1047                         result[i].buffer = f->buf.s;
1048                         fld[i].v.cstr = f->buf.s;
1049                         result[i].buffer_length = STR_BUF_SIZE - 1;
1050                         break;
1051
1052                 case DB_BLOB:
1053                         result[i].buffer_type = MYSQL_TYPE_BLOB;
1054                         if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
1055                         if (f->buf.s == NULL) {
1056                                 ERR("mysql: No memory left\n");
1057                                 err = 1;
1058                                 goto error;
1059                         }
1060                         result[i].buffer = f->buf.s;
1061                         fld[i].v.blob.s = f->buf.s;
1062                         result[i].buffer_length = STR_BUF_SIZE - 1;
1063                         break;
1064
1065                 case DB_NONE:
1066                         /* Eliminates gcc warning */
1067                         break;
1068
1069                 }
1070         }
1071
1072         err = mysql_stmt_bind_result(st, result);
1073         if (err) {
1074                 ERR("mysql: Error while binding result: %s\n", mysql_stmt_error(st));
1075                 goto error;
1076         }
1077
1078         /* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
1079          * creates a copy in the statement and we will update it there
1080          */
1081         if (result) pkg_free(result);
1082         return 0;
1083    
1084  error:
1085         if (result) pkg_free(result);
1086         return err;
1087 }
1088
1089
1090 /**
1091  * Upload database command to the server
1092  * @param cmd  Command to be uploaded
1093  * @return     0 if OK, >0 on DB API errors, <0 on MySQL errors
1094  */
1095 static int upload_cmd(db_cmd_t* cmd)
1096 {
1097     struct my_cmd* res;
1098     struct my_con* mcon;
1099     int err = 0;
1100     
1101     res = DB_GET_PAYLOAD(cmd);
1102     
1103     /* FIXME: The function should take the connection as one of parameters */
1104     mcon = DB_GET_PAYLOAD(cmd->ctx->con[db_payload_idx]);
1105     /* Do not upload the command if the connection is not connected */
1106     if ((mcon->flags & MY_CONNECTED) == 0) {
1107         err = 1;
1108         goto error;
1109     }
1110
1111     /* If there is a previous pre-compiled statement, close it first */
1112     if (res->st) mysql_stmt_close(res->st);
1113     res->st = NULL;
1114     
1115     /* Create a new pre-compiled statement data structure */
1116     res->st = mysql_stmt_init(mcon->con);
1117     if (res->st == NULL) {
1118         ERR("mysql: Error while creating new MySQL_STMT data structure (no memory left)\n");
1119         err = 1;
1120         goto error;
1121     }
1122     
1123     /* Try to upload the command to the server */
1124     if (mysql_stmt_prepare(res->st, res->sql_cmd.s, res->sql_cmd.len)) {
1125         err = mysql_stmt_errno(res->st);    
1126         ERR("mysql: libmysql: %d, %s\n", err, mysql_stmt_error(res->st));
1127         ERR("mysql: An error occurred while uploading command to server\n");
1128     }
1129     if (err == CR_SERVER_LOST ||
1130         err == CR_SERVER_GONE_ERROR) {
1131         /* Connection to the server was lost, mark the connection as
1132          * disconnected. In this case mysql_stmt_prepare invalidates the
1133          * connection internally and calling another mysql function on that
1134          * connection would crash. To make sure that no other mysql function
1135          * gets called unless the connection is reconnected we disconnect it
1136          * explicitly here. This is a workaround for mysql bug #33384. */
1137         my_con_disconnect(cmd->ctx->con[db_payload_idx]);
1138     }
1139     if (err) {
1140         /* Report mysql error to the caller */
1141         err = -1;
1142         goto error;
1143     }
1144     
1145     err = bind_mysql_params(res->st, cmd->vals, cmd->match);
1146     if (err) goto error;
1147     
1148     if (cmd->type == DB_GET || cmd->type == DB_SQL) {
1149         err = check_result(cmd, res);
1150         if (err) goto error;
1151         err = bind_result(res->st, cmd->result);
1152         if (err) goto error;
1153     }
1154     
1155     res->last_reset = mcon->resets;
1156     return 0;
1157     
1158 error:
1159     if (res->st) {
1160         mysql_stmt_close(res->st);
1161         res->st = NULL;
1162     }
1163     return err;
1164 }
1165
1166
1167 int my_cmd(db_cmd_t* cmd)
1168 {
1169         struct my_cmd* res;
1170  
1171         res = (struct my_cmd*)pkg_malloc(sizeof(struct my_cmd));
1172         if (res == NULL) {
1173                 ERR("mysql: No memory left\n");
1174                 goto error;
1175         }
1176         memset(res, '\0', sizeof(struct my_cmd));
1177         /* Fetch all data to client at once by default */
1178         res->flags |= MY_FETCH_ALL;
1179         if (db_drv_init(&res->gen, my_cmd_free) < 0) goto error;
1180
1181         switch(cmd->type) {
1182         case DB_PUT:
1183                 if (DB_FLD_EMPTY(cmd->vals)) {
1184                         BUG("mysql: No parameters provided for DB_PUT in context '%.*s'\n", 
1185                                 cmd->ctx->id.len, ZSW(cmd->ctx->id.s));
1186                         goto error;
1187                 }
1188                 if (build_replace_cmd(&res->sql_cmd, cmd) < 0) goto error;
1189                 break;
1190
1191         case DB_DEL:
1192                 if (build_delete_cmd(&res->sql_cmd, cmd) < 0) goto error;
1193                 break;
1194
1195         case DB_GET:
1196                 if (build_select_cmd(&res->sql_cmd, cmd) < 0) goto error;
1197                 break;
1198
1199         case DB_UPD:
1200                 if (build_update_cmd(&res->sql_cmd, cmd) < 0) goto error;
1201                 break;
1202
1203         case DB_SQL:
1204                 res->sql_cmd.s = (char*)pkg_malloc(cmd->table.len);
1205                 if (res->sql_cmd.s == NULL) {
1206                         ERR("mysql: Out of private memory\n");
1207                         goto error;
1208                 }
1209                 memcpy(res->sql_cmd.s,cmd->table.s, cmd->table.len);
1210                 res->sql_cmd.len = cmd->table.len;
1211         break;
1212         }
1213
1214         DB_SET_PAYLOAD(cmd, res);
1215
1216         /* In order to check all the parameters and results, we need to upload
1217          * the command to the server. We need to do that here before we report
1218          * back that the command was created successfully. Hence, this
1219          * function requires the corresponding connection be established. We
1220          * would not be able to check parameters if we don't do that there and
1221          * that could result in repeated execution failures at runtime.
1222          */
1223         if (upload_cmd(cmd)) goto error;
1224         return 0;
1225
1226  error:
1227         if (res) {
1228                 DB_SET_PAYLOAD(cmd, NULL);
1229                 db_drv_free(&res->gen);
1230                 if (res->sql_cmd.s) pkg_free(res->sql_cmd.s);
1231                 pkg_free(res);
1232         }
1233         return -1;
1234 }
1235
1236
1237 int my_cmd_first(db_res_t* res) {
1238         struct my_cmd* mcmd;
1239
1240         mcmd = DB_GET_PAYLOAD(res->cmd);
1241         switch (mcmd->next_flag) {
1242         case -2: /* table is empty */
1243                 return 1;
1244         case 0:  /* cursor position is 0 */
1245                 return 0;
1246         case 1:  /* next row */
1247         case 2:  /* EOF */
1248                 ERR("mysql: Unbuffered queries do not support cursor reset.\n");
1249                 return -1;
1250         default:
1251                 return my_cmd_next(res);
1252         }
1253 }
1254
1255
1256 int my_cmd_next(db_res_t* res)
1257 {
1258         int ret;
1259         struct my_cmd* mcmd;
1260
1261         mcmd = DB_GET_PAYLOAD(res->cmd);
1262         if (mcmd->next_flag == 2 || mcmd->next_flag == -2) return 1;
1263
1264         if (mcmd->st == NULL) {
1265                 ERR("mysql: Prepared statement not found\n");
1266                 return -1;
1267         }
1268
1269         ret = mysql_stmt_fetch(mcmd->st);
1270         
1271         if (ret == MYSQL_NO_DATA) {
1272                 mcmd->next_flag =  mcmd->next_flag<0?-2:2;
1273                 return 1;
1274         }
1275         /* MYSQL_DATA_TRUNCATED is only defined in mysql >= 5.0 */
1276 #if defined MYSQL_DATA_TRUNCATED
1277         if (ret == MYSQL_DATA_TRUNCATED) {
1278                 int i;
1279                 ERR("mysql: mysql_stmt_fetch, data truncated, fields: %d\n", res->cmd->result_count);
1280                 for (i = 0; i < res->cmd->result_count; i++) {
1281                         if (mcmd->st->bind[i].error /*&& mcmd->st->bind[i].buffer_length*/) {
1282                                 ERR("mysql: truncation, bind %d, length: %lu, buffer_length: %lu\n", 
1283                                         i, *(mcmd->st->bind[i].length), mcmd->st->bind[i].buffer_length);
1284                         }
1285                 }
1286                 ret = 0;
1287         }
1288 #endif
1289         if (mcmd->next_flag <= 0) {
1290                 mcmd->next_flag++;
1291         }
1292         if (ret != 0) {
1293                 ERR("mysql: Error in mysql_stmt_fetch (ret=%d): %s\n", ret, mysql_stmt_error(mcmd->st));
1294                 return -1;
1295         }
1296
1297         if (update_result(res->cmd->result, mcmd->st) < 0) {
1298                 mysql_stmt_free_result(mcmd->st);
1299                 return -1;
1300         }
1301
1302         res->cur_rec->fld = res->cmd->result;
1303         return 0;
1304 }
1305
1306
1307 int my_getopt(db_cmd_t* cmd, char* optname, va_list ap)
1308 {
1309         struct my_cmd* mcmd;
1310         long long* id;
1311         int* val;
1312
1313         mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
1314
1315         if (!strcasecmp("last_id", optname)) {
1316                 id = va_arg(ap, long long*);
1317                 if (id == NULL) {
1318                         BUG("mysql: NULL pointer passed to 'last_id' option\n");
1319                         goto error;
1320                 }
1321
1322                 if (mcmd->st->last_errno != 0) {
1323                         BUG("mysql: Option 'last_id' called but previous command failed, "
1324                                 "check your code\n");
1325                         return -1;
1326                 }
1327
1328                 *id = mysql_stmt_insert_id(mcmd->st);
1329                 if ((*id) == 0) {
1330                         BUG("mysql: Option 'last_id' called but there is no auto-increment"
1331                                 " column in table, SQL command: %.*s\n", STR_FMT(&mcmd->sql_cmd));
1332                         return -1;
1333                 }
1334         } else if (!strcasecmp("fetch_all", optname)) {
1335                 val = va_arg(ap, int*);
1336                 if (val == NULL) {
1337                         BUG("mysql: NULL pointer passed to 'fetch_all' DB option\n");
1338                         goto error;
1339                 }
1340                 *val = mcmd->flags;
1341         } else {
1342                 return 1;
1343         }
1344         return 0;
1345
1346  error:
1347         return -1;
1348 }
1349
1350
1351 int my_setopt(db_cmd_t* cmd, char* optname, va_list ap)
1352 {
1353         struct my_cmd* mcmd;
1354         int* val;
1355
1356         mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
1357         if (!strcasecmp("fetch_all", optname)) {
1358                 val = va_arg(ap, int*);
1359                 if (val != 0) {
1360                         mcmd->flags |= MY_FETCH_ALL;
1361                 } else {
1362                         mcmd->flags &= ~MY_FETCH_ALL;
1363                 }
1364         } else {
1365                 return 1;
1366         }
1367         return 0;
1368 }
1369
1370 /** @} */