db_mysql: safer conditions inside sb_add() for db api v2
[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(nstr->len==0) return 0;
377
378         if ( sb->s==NULL || rsize > sb->size ) {
379                 asize = rsize - sb->size;
380                 new_size = sb->size + (asize / sb->increment  + (asize % sb->increment > 0)) * sb->increment;
381                 newp = pkg_malloc(new_size);
382                 if (!newp) {
383                         ERR("mysql: No memory left\n");
384                         return -1;
385                 }
386                 if (sb->s) {
387                         memcpy(newp, sb->s, sb->len);
388                         pkg_free(sb->s);
389                 }
390                 sb->s = newp;
391                 sb->size = new_size;
392         }
393         memcpy(sb->s + sb->len, nstr->s, nstr->len);
394         sb->len += nstr->len;
395         return 0;
396 }
397
398
399 /**
400  *  Set members of str variable.
401  *  Used for temporary str variables. 
402  */
403 static inline str* set_str(str *str, const char *s)
404 {
405         str->s = (char *)s;
406         str->len = strlen(s);
407         return str;
408 }
409
410
411 /**
412  *  Builds UPDATE statement where cmd->valss specify column name-value pairs
413  *  and cmd->match specify WHERE clause.
414  * @param sql_cmd  SQL statement as a result of this function
415  * @param cmd      input for statement creation
416  */
417 static int build_update_cmd(str* sql_cmd, db_cmd_t* cmd)
418 {
419         struct string_buffer sql_buf = {.s = NULL, .len = 0, .size = 0, .increment = 128};
420         db_fld_t* fld;
421         int i;
422         int rv = 0;
423         str tmpstr;
424
425         rv = sb_add(&sql_buf, &strings[STR_UPDATE]);    /* "UPDATE " */
426         rv |= sb_add(&sql_buf, &cmd->table);                    /* table name */
427         rv |= sb_add(&sql_buf, &strings[STR_SET]);              /* " SET " */
428         if (rv) {
429                 goto err;
430         }
431
432         /* column name-value pairs */
433         for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
434                 rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
435                 rv |= sb_add(&sql_buf, set_str(&tmpstr, " = "));
436                 rv |= sb_add(&sql_buf, &strings[STR_ESC]);
437                 if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ", "));
438         }
439         if (rv) {
440                 goto err;
441         }
442
443         if (!DB_FLD_EMPTY(cmd->match)) {
444                 rv |= sb_add(&sql_buf, &strings[STR_WHERE]);
445
446                 for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
447                         rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
448
449                         switch(fld[i].op) {
450                         case DB_EQ:  rv |= sb_add(&sql_buf, &strings[STR_OP_EQ]);  break;
451                         case DB_NE:  rv |= sb_add(&sql_buf, &strings[STR_OP_NE]);  break;
452                         case DB_LT:  rv |= sb_add(&sql_buf, &strings[STR_OP_LT]);  break;
453                         case DB_GT:  rv |= sb_add(&sql_buf, &strings[STR_OP_GT]);  break;
454                         case DB_LEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_LEQ]); break;
455                         case DB_GEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_GEQ]); break;
456                         }
457                         
458                         rv |= sb_add(&sql_buf, &strings[STR_ESC]);
459                         if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, &strings[STR_AND]);
460                 }
461         }
462         rv |= sb_add(&sql_buf, set_str(&tmpstr, "\0"));
463         if (rv) {
464                 goto err;
465         }
466         sql_cmd->s = sql_buf.s;
467         sql_cmd->len = sql_buf.len;
468         return 0;
469
470 err:
471         if (sql_buf.s) pkg_free(sql_buf.s);
472         return -1;
473 }
474
475
476 static inline void update_field(MYSQL_BIND *param, db_fld_t* fld)
477 {
478         struct my_fld* fp;      /* field payload */
479         struct tm* t;
480         
481         fp = DB_GET_PAYLOAD(fld);
482
483 #ifndef MYSQL_FAKE_NULL
484         fp->is_null = fld->flags & DB_NULL;
485         if (fp->is_null) return;
486 #else
487         if (fld->flags & DB_NULL) {
488                 switch(fld->type) {
489                 case DB_STR:
490                 case DB_CSTR:
491                         param->buffer = FAKE_NULL_STR.s;
492                         fp->length = FAKE_NULL_STR.len;
493                         break;
494                 case DB_INT:
495                         *(int*)param->buffer = FAKE_NULL_INT;
496                         break;
497                 case DB_BLOB:
498                 case DB_DATETIME:
499                 case DB_NONE:
500                 case DB_FLOAT:
501                 case DB_DOUBLE:
502                 case DB_BITMAP:
503                         /* we don't have fake null value for these types */
504                         fp->is_null = DB_NULL;
505                         break;
506                 }
507                 return;
508         }
509 #endif
510         switch(fld->type) {
511         case DB_STR:
512                 param->buffer = fld->v.lstr.s;
513                 fp->length = fld->v.lstr.len;
514                 break;
515
516         case DB_BLOB:
517                 param->buffer = fld->v.blob.s;
518                 fp->length = fld->v.blob.len;
519                 break;
520
521         case DB_CSTR:
522                 param->buffer = (char*)fld->v.cstr;
523                 fp->length = strlen(fld->v.cstr);
524                 break;
525
526         case DB_DATETIME:
527                 t = gmtime(&fld->v.time);
528                 fp->time.second = t->tm_sec;
529                 fp->time.minute = t->tm_min;
530                 fp->time.hour = t->tm_hour;
531                 fp->time.day = t->tm_mday;
532                 fp->time.month = t->tm_mon + 1;
533                 fp->time.year = t->tm_year + 1900;
534                 break;
535                 
536         case DB_NONE:
537         case DB_INT:
538         case DB_FLOAT:
539         case DB_DOUBLE:
540         case DB_BITMAP:
541                 /* No need to do anything for these types */
542                 break;
543
544         }
545 }
546
547
548 /**
549  * Update values of MySQL bound parameters with values from
550  * the DB API.
551  * @param cmd Command structure which contains pointers to MYSQL_STMT and parameters values
552  * @see bind_mysql_params
553  */
554 static inline void set_mysql_params(db_cmd_t* cmd)
555 {
556         struct my_cmd* mcmd;
557         int i;
558
559         mcmd = DB_GET_PAYLOAD(cmd);
560
561         /* FIXME: We are updating internals of the prepared statement here,
562          * this is probably not nice but I could not find another way of
563          * updating the pointer to the buffer without the need to run
564          * mysql_stmt_bind_param again (which would be innefficient)
565          */
566         for(i = 0; i < cmd->vals_count; i++) {
567                 update_field(mcmd->st->params + i, cmd->vals + i);
568         }
569
570         for(i = 0; i < cmd->match_count; i++) {
571                 update_field(mcmd->st->params + cmd->vals_count + i, cmd->match + i);
572         }
573 }
574
575
576 static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
577 {
578         int i;
579         struct my_fld* rp; /* Payload of the current field in result */
580         struct tm t;
581
582         /* Iterate through all the fields returned by MySQL and convert
583          * them to DB API representation if necessary
584          */
585
586         for(i = 0; i < st->field_count; i++) {
587                 rp = DB_GET_PAYLOAD(result + i);
588
589                 if (rp->is_null) {
590                         result[i].flags |= DB_NULL;
591                         continue;
592                 } else {
593                         result[i].flags &= ~DB_NULL;
594                 }
595
596                 switch(result[i].type) {
597                 case DB_STR:
598                         result[i].v.lstr.len = rp->length;
599 #ifdef MYSQL_FAKE_NULL
600                         if (STR_EQ(FAKE_NULL_STR,result[i].v.lstr)) {
601                                 result[i].flags |= DB_NULL;
602                         }
603 #endif
604                         break;
605
606                 case DB_BLOB:
607                         result[i].v.blob.len = rp->length;
608                         break;
609
610                 case DB_CSTR:
611                         if (rp->length < STR_BUF_SIZE) {
612                                 result[i].v.cstr[rp->length] = '\0';
613                         } else {
614                                 /* Truncated field but rp->length contains full size,
615                                  * zero terminated the last byte in the buffer
616                                  */
617                                 result[i].v.cstr[STR_BUF_SIZE - 1] = '\0';
618                         }
619 #ifdef MYSQL_FAKE_NULL
620                         if (strcmp(FAKE_NULL_STR.s,result[i].v.cstr)==0) {
621                                 result[i].flags |= DB_NULL;
622                         }
623 #endif
624                         break;
625                         
626                 case DB_DATETIME:
627                         memset(&t, '\0', sizeof(struct tm));
628                         t.tm_sec = rp->time.second;
629                         t.tm_min = rp->time.minute;
630                         t.tm_hour = rp->time.hour;
631                         t.tm_mday = rp->time.day;
632                         t.tm_mon = rp->time.month - 1;
633                         t.tm_year = rp->time.year - 1900;
634
635                         /* Daylight saving information got lost in the database
636                          * so let timegm to guess it. This eliminates the bug when
637                          * contacts reloaded from the database have different time
638                          * of expiration by one hour when daylight saving is used
639                          */ 
640                         t.tm_isdst = -1;
641 #ifdef HAVE_TIMEGM
642                         result[i].v.time = timegm(&t);
643 #else
644                         result[i].v.time = _timegm(&t);
645 #endif /* HAVE_TIMEGM */
646                         break;
647
648                 case DB_INT:
649 #ifdef MYSQL_FAKE_NULL
650                         if (FAKE_NULL_INT==result[i].v.int4) {
651                                 result[i].flags |= DB_NULL;
652                         }
653                         break;
654 #endif
655                 case DB_NONE:
656                 case DB_FLOAT:
657                 case DB_DOUBLE:
658                 case DB_BITMAP:
659                         /* No need to do anything for these types */
660                         break;
661                 }
662         }
663         
664         return 0;
665 }
666
667
668 /**
669  * This is the main command execution function. The function contains
670  * all the necessary logic to detect reset or disconnected database
671  * connections and uploads commands to the server if necessary.
672  * @param cmd Command to be executed
673  * @return    0 if OK, <0 on MySQL failure, >0 on DB API failure
674  */
675 static int exec_cmd_safe(db_cmd_t* cmd)
676 {
677     int i, err;
678     db_con_t* con;
679     struct my_cmd* mcmd;
680     struct my_con* mcon;
681         
682     /* First things first: retrieve connection info
683      * from the currently active connection and also
684      * mysql payload from the database command
685      */
686     mcmd = DB_GET_PAYLOAD(cmd);
687     con = cmd->ctx->con[db_payload_idx];
688     mcon = DB_GET_PAYLOAD(con);
689     
690     for(i = 0; i <= my_retries; i++) {
691         if ((mcon->flags & MY_CONNECTED) == 0) {
692             /* The connection is disconnected, try to reconnect */
693             if (my_con_connect(con)) {
694                 INFO("mysql: exec_cmd_safe failed to re-connect\n");
695                 continue;
696             }
697         }       
698         
699         /* Next check the number of resets in the database connection, if this
700          * number is higher than the number we keep in my_cmd structure in
701          * last_reset variable then the connection was reset and we need to
702          * upload the command again to the server before executing it, because
703          * the server recycles all server side information upon disconnect.
704          */
705         if (mcon->resets > mcmd->last_reset) {
706             INFO("mysql: Connection reset detected, uploading command to server\n");
707             err = upload_cmd(cmd);
708             if (err < 0) {
709                 INFO("mysql: Error while uploading command\n");
710                 continue;
711             } else if (err > 0) {
712                 /* DB API error, this is a serious problem such as memory
713                  * allocation failure, bail out
714                  */
715                 return 1;
716             }
717         }
718         
719         set_mysql_params(cmd);
720         err = mysql_stmt_execute(mcmd->st);
721         if (err == 0) {
722             /* The command was executed successfully, now fetch all data to
723              * the client if it was requested by the user */
724             if (mcmd->flags & MY_FETCH_ALL) {
725                 err = mysql_stmt_store_result(mcmd->st);
726                 if (err) {
727                     INFO("mysql: Error while fetching data to client.\n");
728                     goto error;
729                 }
730             }
731             return 0;
732         }
733         
734     error:
735         /* Command execution failed, log a message and try to reconnect */
736         INFO("mysql: libmysql: %d, %s\n", mysql_stmt_errno(mcmd->st),
737              mysql_stmt_error(mcmd->st));
738         INFO("mysql: Error while executing command on server, trying to reconnect\n");
739
740         my_con_disconnect(con);
741         if (my_con_connect(con)) {
742             INFO("mysql: Failed to reconnect server\n");
743         } else {
744             INFO("mysql: Successfully reconnected server\n");
745         }
746     }
747     
748     INFO("mysql: Failed to execute command, giving up\n");
749     return -1;
750 }
751
752
753 int my_cmd_exec(db_res_t* res, db_cmd_t* cmd)
754 {
755         struct my_cmd* mcmd;
756
757         mcmd = DB_GET_PAYLOAD(cmd);
758
759         mcmd->next_flag = -1;
760         return exec_cmd_safe(cmd);
761 }
762
763
764 /**
765  * Set MYSQL_BIND item.
766  * @param bind destination
767  * @param fld  source
768  */
769 static void set_field(MYSQL_BIND *bind, db_fld_t* fld)
770 {
771         struct my_fld* f;
772         
773         f = DB_GET_PAYLOAD(fld);
774         bind->is_null = &f->is_null;
775         /* We can do it for all the types here, mysql will ignore it
776          * for fixed-size types such as MYSQL_TYPE_LONG
777          */
778         bind->length = &f->length;
779         switch(fld->type) {
780         case DB_INT:
781         case DB_BITMAP:
782                 bind->buffer_type = MYSQL_TYPE_LONG;
783                 bind->buffer = &fld->v.int4;
784                 break;
785         
786         case DB_FLOAT:
787                 bind->buffer_type = MYSQL_TYPE_FLOAT;
788                 bind->buffer = &fld->v.flt;
789                 break;
790                 
791         case DB_DOUBLE:
792                 bind->buffer_type = MYSQL_TYPE_DOUBLE;
793                 bind->buffer = &fld->v.dbl;
794                 break;
795         
796         case DB_DATETIME:
797                 bind->buffer_type = MYSQL_TYPE_DATETIME;
798                 bind->buffer = &f->time;
799                 break;
800         
801         case DB_STR:
802         case DB_CSTR:
803                 bind->buffer_type = MYSQL_TYPE_VAR_STRING;
804                 bind->buffer = ""; /* Updated on runtime */
805                 break;
806         
807         case DB_BLOB:
808                 bind->buffer_type = MYSQL_TYPE_BLOB;
809                 bind->buffer = ""; /* Updated on runtime */
810                 break;
811         
812         case DB_NONE:
813                 /* Eliminates gcc warning */
814                 break;
815         
816         }
817 }
818
819
820 /**
821  * Bind params, give real values into prepared statement.
822  * Up to two sets of parameters are provided.
823  * Both of them are used in UPDATE command, params1 as colspecs and values and
824  * params2 as WHERE clause. In other cases one set could be enough because values
825  * or match (WHERE clause) is needed.
826  * @param st MySQL command statement
827  * @param params1 first set of params
828  * @param params2 second set of params
829  * @return 0 if OK, <0 on MySQL error, >0 on DB API error
830  * @see update_params
831  */
832 static int bind_mysql_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* params2)
833 {
834         int my_idx, fld_idx;
835         int count1, count2;
836         MYSQL_BIND* my_params;
837         int err = 0;
838
839         /* Calculate the number of parameters */
840         for(count1 = 0; !DB_FLD_EMPTY(params1) && !DB_FLD_LAST(params1[count1]); count1++);
841         for(count2 = 0; !DB_FLD_EMPTY(params2) && !DB_FLD_LAST(params2[count2]); count2++);
842         if (st->param_count != count1 + count2) {
843                 BUG("mysql: Number of parameters in SQL command does not match number of DB API parameters\n");
844                 return 1;
845         }
846         
847         my_params = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * (count1 + count2));
848         if (my_params == NULL) {
849                 ERR("mysql: No memory left\n");
850                 return -1;
851         }
852         memset(my_params, '\0', sizeof(MYSQL_BIND) * (count1 + count2));
853
854         /* params1 */
855         my_idx = 0;
856         for (fld_idx = 0; fld_idx < count1; fld_idx++, my_idx++) {
857                 set_field(&my_params[my_idx], params1 + fld_idx);
858         }
859         /* params2 */
860         for (fld_idx = 0; fld_idx < count2; fld_idx++, my_idx++) {
861                 set_field(&my_params[my_idx], params2 + fld_idx);
862         }
863
864         err = mysql_stmt_bind_param(st, my_params);
865         if (err) {
866                 ERR("mysql: libmysqlclient: %d, %s\n", 
867                         mysql_stmt_errno(st), mysql_stmt_error(st));
868                 goto error;
869         }
870
871         /* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
872          * creates a copy in the statement and we will update it there
873          */
874         pkg_free(my_params);
875         return err;
876    
877  error:
878         if (my_params) pkg_free(my_params);
879         return err;
880 }
881
882
883 /*
884  * FIXME: This function will only work if we have one db connection
885  * in every context, otherwise it would initialize the result set
886  * from the first connection in the context.
887  */
888 static int check_result(db_cmd_t* cmd, struct my_cmd* payload)
889 {
890         int i, n;
891         MYSQL_FIELD *fld;
892         MYSQL_RES *meta = NULL;
893
894         meta = mysql_stmt_result_metadata(payload->st);
895         if (meta == NULL) {
896                 /* No error means no result set to be checked */
897                 if (mysql_stmt_errno(payload->st) == 0) return 0;
898                 ERR("mysql: Error while getting metadata of SQL command: %d, %s\n",
899                         mysql_stmt_errno(payload->st), mysql_stmt_error(payload->st));
900                 return -1;
901         }
902         n = mysql_num_fields(meta);
903         if (cmd->result == NULL) {
904                 /* The result set parameter of db_cmd function was empty, that
905                  * means the command is select * and we have to create the array
906                  * of result fields in the cmd structure manually.
907                  */
908                 cmd->result = db_fld(n + 1);
909                 cmd->result_count = n;
910                 for(i = 0; i < cmd->result_count; i++) {
911                         struct my_fld *f;
912                         if (my_fld(cmd->result + i, cmd->table.s) < 0) goto error;
913                         f = DB_GET_PAYLOAD(cmd->result + i);
914                         fld = mysql_fetch_field_direct(meta, i);
915                         f->name = pkg_malloc(strlen(fld->name)+1);
916                         if (f->name == NULL) {
917                                 ERR("mysql: Out of private memory\n");
918                                 goto error;
919                         }
920                         strcpy(f->name, fld->name);
921                         cmd->result[i].name = f->name;
922                 }
923         } else {
924                 if (cmd->result_count != n) {
925                         BUG("mysql: Number of fields in MySQL result does not match number of parameters in DB API\n");
926                         goto error;
927                 }
928         }
929
930         /* Now iterate through all the columns in the result set and replace
931          * any occurrence of DB_UNKNOWN type with the type of the column
932          * retrieved from the database and if no column name was provided then
933          * update it from the database as well. 
934          */
935         for(i = 0; i < cmd->result_count; i++) {
936                 fld = mysql_fetch_field_direct(meta, i);
937                 if (cmd->result[i].type != DB_NONE) continue;
938                 switch(fld->type) {
939                 case MYSQL_TYPE_TINY:
940                 case MYSQL_TYPE_SHORT:
941                 case MYSQL_TYPE_INT24:
942                 case MYSQL_TYPE_LONG:
943                         cmd->result[i].type = DB_INT;
944                         break;
945
946                 case MYSQL_TYPE_FLOAT:
947                         cmd->result[i].type = DB_FLOAT;
948                         break;
949
950                 case MYSQL_TYPE_DOUBLE:
951                         cmd->result[i].type = DB_DOUBLE;
952                         break;
953
954                 case MYSQL_TYPE_TIMESTAMP:
955                 case MYSQL_TYPE_DATETIME:
956                         cmd->result[i].type = DB_DATETIME;
957                         break;
958
959                 case MYSQL_TYPE_STRING:
960                 case MYSQL_TYPE_VAR_STRING:
961                         cmd->result[i].type = DB_STR;
962                         break;
963
964                 default:
965                         ERR("mysql: Unsupported MySQL column type: %d, table: %s, column: %s\n",
966                                 fld->type, cmd->table.s, fld->name);
967                         goto error;
968                 }
969         }
970         
971         if (meta) mysql_free_result(meta);
972         return 0;
973
974 error:
975         if (meta) mysql_free_result(meta);
976         return 1;
977 }
978
979
980 /* FIXME: Add support for DB_NONE, in this case the function should determine
981  * the type of the column in the database and set the field type appropriately.
982  * This function must be called after check_result.
983  */
984 static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
985 {
986         int i, n, err = 0;
987         struct my_fld* f;
988         MYSQL_BIND* result;
989
990         /* Calculate the number of fields in the result */
991         for(n = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[n]); n++);
992         /* Return immediately if there are no fields in the result set */
993         if (n == 0) return 0;
994
995         result = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * n);
996         if (result == NULL) {
997                 ERR("mysql: No memory left\n");
998                 return 1;
999         }
1000         memset(result, '\0', sizeof(MYSQL_BIND) * n);
1001         
1002         for(i = 0; i < n; i++) {
1003                 f = DB_GET_PAYLOAD(fld + i);
1004                 result[i].is_null = &f->is_null;
1005                 /* We can do it for all the types here, mysql will ignore it
1006                  * for fixed-size types such as MYSQL_TYPE_LONG
1007                  */
1008                 result[i].length = &f->length;
1009                 switch(fld[i].type) {
1010                 case DB_INT:
1011                 case DB_BITMAP:
1012                         result[i].buffer_type = MYSQL_TYPE_LONG;
1013                         result[i].buffer = &fld[i].v.int4;
1014                         break;
1015
1016                 case DB_FLOAT:
1017                         result[i].buffer_type = MYSQL_TYPE_FLOAT;
1018                         result[i].buffer = &fld[i].v.flt;
1019                         break;
1020                         
1021                 case DB_DOUBLE:
1022                         result[i].buffer_type = MYSQL_TYPE_DOUBLE;
1023                         result[i].buffer = &fld[i].v.dbl;
1024                         break;
1025
1026                 case DB_DATETIME:
1027                         result[i].buffer_type = MYSQL_TYPE_DATETIME;
1028                         result[i].buffer = &f->time;
1029                         break;
1030
1031                 case DB_STR:
1032                         result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
1033                         if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
1034                         if (f->buf.s == NULL) {
1035                                 ERR("mysql: No memory left\n");
1036                                 err = 1;
1037                                 goto error;
1038                         }
1039                         result[i].buffer = f->buf.s;
1040                         fld[i].v.lstr.s = f->buf.s;
1041                         result[i].buffer_length = STR_BUF_SIZE - 1;
1042                         break;
1043
1044                 case DB_CSTR:
1045                         result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
1046                         if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
1047                         if (f->buf.s == NULL) {
1048                                 ERR("mysql: No memory left\n");
1049                                 err = 1;
1050                                 goto error;
1051                         }
1052                         result[i].buffer = f->buf.s;
1053                         fld[i].v.cstr = f->buf.s;
1054                         result[i].buffer_length = STR_BUF_SIZE - 1;
1055                         break;
1056
1057                 case DB_BLOB:
1058                         result[i].buffer_type = MYSQL_TYPE_BLOB;
1059                         if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
1060                         if (f->buf.s == NULL) {
1061                                 ERR("mysql: No memory left\n");
1062                                 err = 1;
1063                                 goto error;
1064                         }
1065                         result[i].buffer = f->buf.s;
1066                         fld[i].v.blob.s = f->buf.s;
1067                         result[i].buffer_length = STR_BUF_SIZE - 1;
1068                         break;
1069
1070                 case DB_NONE:
1071                         /* Eliminates gcc warning */
1072                         break;
1073
1074                 }
1075         }
1076
1077         err = mysql_stmt_bind_result(st, result);
1078         if (err) {
1079                 ERR("mysql: Error while binding result: %s\n", mysql_stmt_error(st));
1080                 goto error;
1081         }
1082
1083         /* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
1084          * creates a copy in the statement and we will update it there
1085          */
1086         if (result) pkg_free(result);
1087         return 0;
1088    
1089  error:
1090         if (result) pkg_free(result);
1091         return err;
1092 }
1093
1094
1095 /**
1096  * Upload database command to the server
1097  * @param cmd  Command to be uploaded
1098  * @return     0 if OK, >0 on DB API errors, <0 on MySQL errors
1099  */
1100 static int upload_cmd(db_cmd_t* cmd)
1101 {
1102     struct my_cmd* res;
1103     struct my_con* mcon;
1104     int err = 0;
1105     
1106     res = DB_GET_PAYLOAD(cmd);
1107     
1108     /* FIXME: The function should take the connection as one of parameters */
1109     mcon = DB_GET_PAYLOAD(cmd->ctx->con[db_payload_idx]);
1110     /* Do not upload the command if the connection is not connected */
1111     if ((mcon->flags & MY_CONNECTED) == 0) {
1112         err = 1;
1113         goto error;
1114     }
1115
1116     /* If there is a previous pre-compiled statement, close it first */
1117     if (res->st) mysql_stmt_close(res->st);
1118     res->st = NULL;
1119     
1120     /* Create a new pre-compiled statement data structure */
1121     res->st = mysql_stmt_init(mcon->con);
1122     if (res->st == NULL) {
1123         ERR("mysql: Error while creating new MySQL_STMT data structure (no memory left)\n");
1124         err = 1;
1125         goto error;
1126     }
1127     
1128     /* Try to upload the command to the server */
1129     if (mysql_stmt_prepare(res->st, res->sql_cmd.s, res->sql_cmd.len)) {
1130         err = mysql_stmt_errno(res->st);    
1131         ERR("mysql: libmysql: %d, %s\n", err, mysql_stmt_error(res->st));
1132         ERR("mysql: An error occurred while uploading command to server\n");
1133     }
1134     if (err == CR_SERVER_LOST ||
1135         err == CR_SERVER_GONE_ERROR) {
1136         /* Connection to the server was lost, mark the connection as
1137          * disconnected. In this case mysql_stmt_prepare invalidates the
1138          * connection internally and calling another mysql function on that
1139          * connection would crash. To make sure that no other mysql function
1140          * gets called unless the connection is reconnected we disconnect it
1141          * explicitly here. This is a workaround for mysql bug #33384. */
1142         my_con_disconnect(cmd->ctx->con[db_payload_idx]);
1143     }
1144     if (err) {
1145         /* Report mysql error to the caller */
1146         err = -1;
1147         goto error;
1148     }
1149     
1150     err = bind_mysql_params(res->st, cmd->vals, cmd->match);
1151     if (err) goto error;
1152     
1153     if (cmd->type == DB_GET || cmd->type == DB_SQL) {
1154         err = check_result(cmd, res);
1155         if (err) goto error;
1156         err = bind_result(res->st, cmd->result);
1157         if (err) goto error;
1158     }
1159     
1160     res->last_reset = mcon->resets;
1161     return 0;
1162     
1163 error:
1164     if (res->st) {
1165         mysql_stmt_close(res->st);
1166         res->st = NULL;
1167     }
1168     return err;
1169 }
1170
1171
1172 int my_cmd(db_cmd_t* cmd)
1173 {
1174         struct my_cmd* res;
1175  
1176         res = (struct my_cmd*)pkg_malloc(sizeof(struct my_cmd));
1177         if (res == NULL) {
1178                 ERR("mysql: No memory left\n");
1179                 goto error;
1180         }
1181         memset(res, '\0', sizeof(struct my_cmd));
1182         /* Fetch all data to client at once by default */
1183         res->flags |= MY_FETCH_ALL;
1184         if (db_drv_init(&res->gen, my_cmd_free) < 0) goto error;
1185
1186         switch(cmd->type) {
1187         case DB_PUT:
1188                 if (DB_FLD_EMPTY(cmd->vals)) {
1189                         BUG("mysql: No parameters provided for DB_PUT in context '%.*s'\n", 
1190                                 cmd->ctx->id.len, ZSW(cmd->ctx->id.s));
1191                         goto error;
1192                 }
1193                 if (build_replace_cmd(&res->sql_cmd, cmd) < 0) goto error;
1194                 break;
1195
1196         case DB_DEL:
1197                 if (build_delete_cmd(&res->sql_cmd, cmd) < 0) goto error;
1198                 break;
1199
1200         case DB_GET:
1201                 if (build_select_cmd(&res->sql_cmd, cmd) < 0) goto error;
1202                 break;
1203
1204         case DB_UPD:
1205                 if (build_update_cmd(&res->sql_cmd, cmd) < 0) goto error;
1206                 break;
1207
1208         case DB_SQL:
1209                 res->sql_cmd.s = (char*)pkg_malloc(cmd->table.len);
1210                 if (res->sql_cmd.s == NULL) {
1211                         ERR("mysql: Out of private memory\n");
1212                         goto error;
1213                 }
1214                 memcpy(res->sql_cmd.s,cmd->table.s, cmd->table.len);
1215                 res->sql_cmd.len = cmd->table.len;
1216         break;
1217         }
1218
1219         DB_SET_PAYLOAD(cmd, res);
1220
1221         /* In order to check all the parameters and results, we need to upload
1222          * the command to the server. We need to do that here before we report
1223          * back that the command was created successfully. Hence, this
1224          * function requires the corresponding connection be established. We
1225          * would not be able to check parameters if we don't do that there and
1226          * that could result in repeated execution failures at runtime.
1227          */
1228         if (upload_cmd(cmd)) goto error;
1229         return 0;
1230
1231  error:
1232         if (res) {
1233                 DB_SET_PAYLOAD(cmd, NULL);
1234                 db_drv_free(&res->gen);
1235                 if (res->sql_cmd.s) pkg_free(res->sql_cmd.s);
1236                 pkg_free(res);
1237         }
1238         return -1;
1239 }
1240
1241
1242 int my_cmd_first(db_res_t* res) {
1243         struct my_cmd* mcmd;
1244
1245         mcmd = DB_GET_PAYLOAD(res->cmd);
1246         switch (mcmd->next_flag) {
1247         case -2: /* table is empty */
1248                 return 1;
1249         case 0:  /* cursor position is 0 */
1250                 return 0;
1251         case 1:  /* next row */
1252         case 2:  /* EOF */
1253                 ERR("mysql: Unbuffered queries do not support cursor reset.\n");
1254                 return -1;
1255         default:
1256                 return my_cmd_next(res);
1257         }
1258 }
1259
1260
1261 int my_cmd_next(db_res_t* res)
1262 {
1263         int ret;
1264         struct my_cmd* mcmd;
1265
1266         mcmd = DB_GET_PAYLOAD(res->cmd);
1267         if (mcmd->next_flag == 2 || mcmd->next_flag == -2) return 1;
1268
1269         if (mcmd->st == NULL) {
1270                 ERR("mysql: Prepared statement not found\n");
1271                 return -1;
1272         }
1273
1274         ret = mysql_stmt_fetch(mcmd->st);
1275         
1276         if (ret == MYSQL_NO_DATA) {
1277                 mcmd->next_flag =  mcmd->next_flag<0?-2:2;
1278                 return 1;
1279         }
1280         /* MYSQL_DATA_TRUNCATED is only defined in mysql >= 5.0 */
1281 #if defined MYSQL_DATA_TRUNCATED
1282         if (ret == MYSQL_DATA_TRUNCATED) {
1283                 int i;
1284                 ERR("mysql: mysql_stmt_fetch, data truncated, fields: %d\n", res->cmd->result_count);
1285                 for (i = 0; i < res->cmd->result_count; i++) {
1286                         if (mcmd->st->bind[i].error /*&& mcmd->st->bind[i].buffer_length*/) {
1287                                 ERR("mysql: truncation, bind %d, length: %lu, buffer_length: %lu\n", 
1288                                         i, *(mcmd->st->bind[i].length), mcmd->st->bind[i].buffer_length);
1289                         }
1290                 }
1291                 ret = 0;
1292         }
1293 #endif
1294         if (mcmd->next_flag <= 0) {
1295                 mcmd->next_flag++;
1296         }
1297         if (ret != 0) {
1298                 ERR("mysql: Error in mysql_stmt_fetch (ret=%d): %s\n", ret, mysql_stmt_error(mcmd->st));
1299                 return -1;
1300         }
1301
1302         if (update_result(res->cmd->result, mcmd->st) < 0) {
1303                 mysql_stmt_free_result(mcmd->st);
1304                 return -1;
1305         }
1306
1307         res->cur_rec->fld = res->cmd->result;
1308         return 0;
1309 }
1310
1311
1312 int my_getopt(db_cmd_t* cmd, char* optname, va_list ap)
1313 {
1314         struct my_cmd* mcmd;
1315         long long* id;
1316         int* val;
1317
1318         mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
1319
1320         if (!strcasecmp("last_id", optname)) {
1321                 id = va_arg(ap, long long*);
1322                 if (id == NULL) {
1323                         BUG("mysql: NULL pointer passed to 'last_id' option\n");
1324                         goto error;
1325                 }
1326
1327                 if (mcmd->st->last_errno != 0) {
1328                         BUG("mysql: Option 'last_id' called but previous command failed, "
1329                                 "check your code\n");
1330                         return -1;
1331                 }
1332
1333                 *id = mysql_stmt_insert_id(mcmd->st);
1334                 if ((*id) == 0) {
1335                         BUG("mysql: Option 'last_id' called but there is no auto-increment"
1336                                 " column in table, SQL command: %.*s\n", STR_FMT(&mcmd->sql_cmd));
1337                         return -1;
1338                 }
1339         } else if (!strcasecmp("fetch_all", optname)) {
1340                 val = va_arg(ap, int*);
1341                 if (val == NULL) {
1342                         BUG("mysql: NULL pointer passed to 'fetch_all' DB option\n");
1343                         goto error;
1344                 }
1345                 *val = mcmd->flags;
1346         } else {
1347                 return 1;
1348         }
1349         return 0;
1350
1351  error:
1352         return -1;
1353 }
1354
1355
1356 int my_setopt(db_cmd_t* cmd, char* optname, va_list ap)
1357 {
1358         struct my_cmd* mcmd;
1359         int* val;
1360
1361         mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
1362         if (!strcasecmp("fetch_all", optname)) {
1363                 val = va_arg(ap, int*);
1364                 if (val != 0) {
1365                         mcmd->flags |= MY_FETCH_ALL;
1366                 } else {
1367                         mcmd->flags &= ~MY_FETCH_ALL;
1368                 }
1369         } else {
1370                 return 1;
1371         }
1372         return 0;
1373 }
1374
1375 /** @} */