1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369 |
- /*
- * Copyright (C) 2001-2003 FhG Fokus
- * Copyright (C) 2006-2007 iptelorg GmbH
- *
- * This file is part of Kamailio, a free SIP server.
- *
- * Kamailio is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version
- *
- * Kamailio is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
- /** @addtogroup mysql
- * @{
- */
- /* the following macro will break the compile on solaris */
- #if !defined (__SVR4) && !defined (__sun)
- #define _XOPEN_SOURCE 4 /* bsd */
- #endif
- #define _XOPEN_SOURCE_EXTENDED 1 /* solaris */
- #define _SVID_SOURCE 1 /* timegm */
- #include "my_cmd.h"
- #include "my_con.h"
- #include "mysql_mod.h"
- #include "my_fld.h"
- #include "../../mem/mem.h"
- #include "../../str.h"
- #include "../../lib/srdb2/db_cmd.h"
- #include "../../ut.h"
- #include "../../dprint.h"
- #include <strings.h>
- #include <stdio.h>
- #include <time.h> /*strptime, XOPEN issue must be >=4 */
- #include <string.h>
- #include <mysql/errmsg.h>
- #include <mysql/mysqld_error.h>
- #define STR_BUF_SIZE 1024
- #ifdef MYSQL_FAKE_NULL
- #define FAKE_NULL_STRING "[~NULL~]"
- static str FAKE_NULL_STR = STR_STATIC_INIT(FAKE_NULL_STRING);
- /* avoid warning: this decimal constant is unsigned only in ISO C90 :-) */
- #define FAKE_NULL_INT (-2147483647 - 1)
- #endif
- enum {
- STR_DELETE,
- STR_INSERT,
- STR_UPDATE,
- STR_SELECT,
- STR_REPLACE,
- STR_SET,
- STR_WHERE,
- STR_IS,
- STR_AND,
- STR_OR,
- STR_ESC,
- STR_OP_EQ,
- STR_OP_NE,
- STR_OP_LT,
- STR_OP_GT,
- STR_OP_LEQ,
- STR_OP_GEQ,
- STR_VALUES,
- STR_FROM
- };
- static str strings[] = {
- STR_STATIC_INIT("delete from "),
- STR_STATIC_INIT("insert into "),
- STR_STATIC_INIT("update "),
- STR_STATIC_INIT("select "),
- STR_STATIC_INIT("replace "),
- STR_STATIC_INIT(" set "),
- STR_STATIC_INIT(" where "),
- STR_STATIC_INIT(" is "),
- STR_STATIC_INIT(" and "),
- STR_STATIC_INIT(" or "),
- STR_STATIC_INIT("?"),
- STR_STATIC_INIT("="),
- STR_STATIC_INIT("!="),
- STR_STATIC_INIT("<"),
- STR_STATIC_INIT(">"),
- STR_STATIC_INIT("<="),
- STR_STATIC_INIT(">="),
- STR_STATIC_INIT(") values ("),
- STR_STATIC_INIT(" from ")
- };
- #define APPEND_STR(p, str) do { \
- memcpy((p), (str).s, (str).len); \
- (p) += (str).len; \
- } while(0)
- #define APPEND_CSTR(p, cstr) do { \
- int _len = strlen(cstr); \
- memcpy((p), (cstr), _len); \
- (p) += _len; \
- } while(0)
- static int upload_cmd(db_cmd_t* cmd);
- static void my_cmd_free(db_cmd_t* cmd, struct my_cmd* payload)
- {
- db_drv_free(&payload->gen);
- if (payload->sql_cmd.s) pkg_free(payload->sql_cmd.s);
- if (payload->st) mysql_stmt_close(payload->st);
- pkg_free(payload);
- }
- /** Builds a DELETE SQL statement.The function builds DELETE statement where
- * cmd->match specify WHERE clause.
- * @param sql_cmd SQL statement as a result of this function
- * @param cmd input for statement creation
- * @return -1 on error, 0 on success
- */
- static int build_delete_cmd(str* sql_cmd, db_cmd_t* cmd)
- {
- db_fld_t* fld;
- int i;
- char* p;
- sql_cmd->len = strings[STR_DELETE].len;
- sql_cmd->len += cmd->table.len;
- if (!DB_FLD_EMPTY(cmd->match)) {
- sql_cmd->len += strings[STR_WHERE].len;
- for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
- sql_cmd->len += strlen(fld[i].name);
- switch(fld[i].op) {
- case DB_EQ: sql_cmd->len += strings[STR_OP_EQ].len; break;
- case DB_NE: sql_cmd->len += strings[STR_OP_NE].len; break;
- case DB_LT: sql_cmd->len += strings[STR_OP_LT].len; break;
- case DB_GT: sql_cmd->len += strings[STR_OP_GT].len; break;
- case DB_LEQ: sql_cmd->len += strings[STR_OP_LEQ].len; break;
- case DB_GEQ: sql_cmd->len += strings[STR_OP_GEQ].len; break;
- default:
- ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
- return -1;
- }
- sql_cmd->len += strings[STR_ESC].len;
-
- if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
- }
- }
- sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
- if (sql_cmd->s == NULL) {
- ERR("mysql: No memory left\n");
- return -1;
- }
- p = sql_cmd->s;
-
- APPEND_STR(p, strings[STR_DELETE]);
- APPEND_STR(p, cmd->table);
- if (!DB_FLD_EMPTY(cmd->match)) {
- APPEND_STR(p, strings[STR_WHERE]);
- for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
- APPEND_CSTR(p, fld[i].name);
- switch(fld[i].op) {
- case DB_EQ: APPEND_STR(p, strings[STR_OP_EQ]); break;
- case DB_NE: APPEND_STR(p, strings[STR_OP_NE]); break;
- case DB_LT: APPEND_STR(p, strings[STR_OP_LT]); break;
- case DB_GT: APPEND_STR(p, strings[STR_OP_GT]); break;
- case DB_LEQ: APPEND_STR(p, strings[STR_OP_LEQ]); break;
- case DB_GEQ: APPEND_STR(p, strings[STR_OP_GEQ]); break;
- }
-
- APPEND_STR(p, strings[STR_ESC]);
- if (!DB_FLD_LAST(fld[i + 1])) APPEND_STR(p, strings[STR_AND]);
- }
- }
-
- *p = '\0';
- return 0;
- }
- /**
- * Builds SELECT statement where cmd->values specify column names
- * and cmd->match specify WHERE clause.
- * @param sql_cmd SQL statement as a result of this function
- * @param cmd input for statement creation
- */
- static int build_select_cmd(str* sql_cmd, db_cmd_t* cmd)
- {
- db_fld_t* fld;
- int i;
- char* p;
- sql_cmd->len = strings[STR_SELECT].len;
- if (DB_FLD_EMPTY(cmd->result)) {
- sql_cmd->len += 1; /* "*" */
- } else {
- for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
- sql_cmd->len += strlen(fld[i].name);
- if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += 1; /* , */
- }
- }
- sql_cmd->len += strings[STR_FROM].len;
- sql_cmd->len += cmd->table.len;
- if (!DB_FLD_EMPTY(cmd->match)) {
- sql_cmd->len += strings[STR_WHERE].len;
- for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
- sql_cmd->len += strlen(fld[i].name);
- switch(fld[i].op) {
- case DB_EQ: sql_cmd->len += strings[STR_OP_EQ].len; break;
- case DB_NE: sql_cmd->len += strings[STR_OP_NE].len; break;
- case DB_LT: sql_cmd->len += strings[STR_OP_LT].len; break;
- case DB_GT: sql_cmd->len += strings[STR_OP_GT].len; break;
- case DB_LEQ: sql_cmd->len += strings[STR_OP_LEQ].len; break;
- case DB_GEQ: sql_cmd->len += strings[STR_OP_GEQ].len; break;
- default:
- ERR("mysql: Unsupported db_fld operator %d\n", fld[i].op);
- return -1;
- }
- sql_cmd->len += strings[STR_ESC].len;
-
- if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += strings[STR_AND].len;
- }
- }
- sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
- if (sql_cmd->s == NULL) {
- ERR("mysql: No memory left\n");
- return -1;
- }
- p = sql_cmd->s;
-
- APPEND_STR(p, strings[STR_SELECT]);
- if (DB_FLD_EMPTY(cmd->result)) {
- *p++ = '*';
- } else {
- for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
- APPEND_CSTR(p, fld[i].name);
- if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
- }
- }
- APPEND_STR(p, strings[STR_FROM]);
- APPEND_STR(p, cmd->table);
- if (!DB_FLD_EMPTY(cmd->match)) {
- APPEND_STR(p, strings[STR_WHERE]);
- for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
- APPEND_CSTR(p, fld[i].name);
- switch(fld[i].op) {
- case DB_EQ: APPEND_STR(p, strings[STR_OP_EQ]); break;
- case DB_NE: APPEND_STR(p, strings[STR_OP_NE]); break;
- case DB_LT: APPEND_STR(p, strings[STR_OP_LT]); break;
- case DB_GT: APPEND_STR(p, strings[STR_OP_GT]); break;
- case DB_LEQ: APPEND_STR(p, strings[STR_OP_LEQ]); break;
- case DB_GEQ: APPEND_STR(p, strings[STR_OP_GEQ]); break;
- }
-
- APPEND_STR(p, strings[STR_ESC]);
- if (!DB_FLD_LAST(fld[i + 1])) APPEND_STR(p, strings[STR_AND]);
- }
- }
- *p = '\0';
- return 0;
- }
- /**
- * Builds REPLACE statement where cmd->values specify column names.
- * @param sql_cmd SQL statement as a result of this function
- * @param cmd input for statement creation
- */
- static int build_replace_cmd(str* sql_cmd, db_cmd_t* cmd)
- {
- db_fld_t* fld;
- int i;
- char* p;
- sql_cmd->len = strings[STR_REPLACE].len;
- sql_cmd->len += cmd->table.len;
- sql_cmd->len += 2; /* " (" */
- for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
- sql_cmd->len += strlen(fld[i].name);
- sql_cmd->len += strings[STR_ESC].len;
- if (!DB_FLD_LAST(fld[i + 1])) sql_cmd->len += 2; /* , twice */
- }
- sql_cmd->len += strings[STR_VALUES].len;
- sql_cmd->len += 1; /* ) */
- sql_cmd->s = pkg_malloc(sql_cmd->len + 1);
- if (sql_cmd->s == NULL) {
- ERR("mysql: No memory left\n");
- return -1;
- }
- p = sql_cmd->s;
-
- APPEND_STR(p, strings[STR_REPLACE]);
- APPEND_STR(p, cmd->table);
- *p++ = ' ';
- *p++ = '(';
- for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
- APPEND_CSTR(p, fld[i].name);
- if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
- }
- APPEND_STR(p, strings[STR_VALUES]);
- for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
- APPEND_STR(p, strings[STR_ESC]);
- if (!DB_FLD_LAST(fld[i + 1])) *p++ = ',';
- }
- *p++ = ')';
- *p = '\0';
- return 0;
- }
- /**
- * Reallocatable string buffer.
- */
- struct string_buffer {
- char *s; /**< allocated memory itself */
- int len; /**< used memory */
- int size; /**< total size of allocated memory */
- int increment; /**< increment when realloc is necessary */
- };
- /**
- * Add new string into string buffer.
- * @param sb string buffer
- * @param nstr string to add
- * @return 0 if OK, -1 if failed
- */
- static inline int sb_add(struct string_buffer *sb, str *nstr)
- {
- int new_size = 0;
- int rsize = sb->len + nstr->len;
- int asize;
- char *newp;
-
- if ( rsize > sb->size ) {
- asize = rsize - sb->size;
- new_size = sb->size + (asize / sb->increment + (asize % sb->increment > 0)) * sb->increment;
- newp = pkg_malloc(new_size);
- if (!newp) {
- ERR("mysql: No memory left\n");
- return -1;
- }
- if (sb->s) {
- memcpy(newp, sb->s, sb->len);
- pkg_free(sb->s);
- }
- sb->s = newp;
- sb->size = new_size;
- }
- memcpy(sb->s + sb->len, nstr->s, nstr->len);
- sb->len += nstr->len;
- return 0;
- }
- /**
- * Set members of str variable.
- * Used for temporary str variables.
- */
- static inline str* set_str(str *str, const char *s)
- {
- str->s = (char *)s;
- str->len = strlen(s);
- return str;
- }
- /**
- * Builds UPDATE statement where cmd->valss specify column name-value pairs
- * and cmd->match specify WHERE clause.
- * @param sql_cmd SQL statement as a result of this function
- * @param cmd input for statement creation
- */
- static int build_update_cmd(str* sql_cmd, db_cmd_t* cmd)
- {
- struct string_buffer sql_buf = {.s = NULL, .len = 0, .size = 0, .increment = 128};
- db_fld_t* fld;
- int i;
- int rv = 0;
- str tmpstr;
- rv = sb_add(&sql_buf, &strings[STR_UPDATE]); /* "UPDATE " */
- rv |= sb_add(&sql_buf, &cmd->table); /* table name */
- rv |= sb_add(&sql_buf, &strings[STR_SET]); /* " SET " */
- /* column name-value pairs */
- for(i = 0, fld = cmd->vals; !DB_FLD_LAST(fld[i]); i++) {
- rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
- rv |= sb_add(&sql_buf, set_str(&tmpstr, " = "));
- rv |= sb_add(&sql_buf, &strings[STR_ESC]);
- if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ", "));
- }
- if (rv) {
- goto err;
- }
- if (!DB_FLD_EMPTY(cmd->match)) {
- rv |= sb_add(&sql_buf, &strings[STR_WHERE]);
- for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
- rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
- switch(fld[i].op) {
- case DB_EQ: rv |= sb_add(&sql_buf, &strings[STR_OP_EQ]); break;
- case DB_NE: rv |= sb_add(&sql_buf, &strings[STR_OP_NE]); break;
- case DB_LT: rv |= sb_add(&sql_buf, &strings[STR_OP_LT]); break;
- case DB_GT: rv |= sb_add(&sql_buf, &strings[STR_OP_GT]); break;
- case DB_LEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_LEQ]); break;
- case DB_GEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_GEQ]); break;
- }
-
- rv |= sb_add(&sql_buf, &strings[STR_ESC]);
- if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, &strings[STR_AND]);
- }
- }
- rv |= sb_add(&sql_buf, set_str(&tmpstr, "\0"));
- if (rv) {
- goto err;
- }
- sql_cmd->s = sql_buf.s;
- sql_cmd->len = sql_buf.len;
- return 0;
- err:
- if (sql_buf.s) pkg_free(sql_buf.s);
- return -1;
- }
- static inline void update_field(MYSQL_BIND *param, db_fld_t* fld)
- {
- struct my_fld* fp; /* field payload */
- struct tm* t;
-
- fp = DB_GET_PAYLOAD(fld);
- #ifndef MYSQL_FAKE_NULL
- fp->is_null = fld->flags & DB_NULL;
- if (fp->is_null) return;
- #else
- if (fld->flags & DB_NULL) {
- switch(fld->type) {
- case DB_STR:
- case DB_CSTR:
- param->buffer = FAKE_NULL_STR.s;
- fp->length = FAKE_NULL_STR.len;
- break;
- case DB_INT:
- *(int*)param->buffer = FAKE_NULL_INT;
- break;
- case DB_BLOB:
- case DB_DATETIME:
- case DB_NONE:
- case DB_FLOAT:
- case DB_DOUBLE:
- case DB_BITMAP:
- /* we don't have fake null value for these types */
- fp->is_null = DB_NULL;
- break;
- }
- return;
- }
- #endif
- switch(fld->type) {
- case DB_STR:
- param->buffer = fld->v.lstr.s;
- fp->length = fld->v.lstr.len;
- break;
- case DB_BLOB:
- param->buffer = fld->v.blob.s;
- fp->length = fld->v.blob.len;
- break;
- case DB_CSTR:
- param->buffer = (char*)fld->v.cstr;
- fp->length = strlen(fld->v.cstr);
- break;
- case DB_DATETIME:
- t = gmtime(&fld->v.time);
- fp->time.second = t->tm_sec;
- fp->time.minute = t->tm_min;
- fp->time.hour = t->tm_hour;
- fp->time.day = t->tm_mday;
- fp->time.month = t->tm_mon + 1;
- fp->time.year = t->tm_year + 1900;
- break;
-
- case DB_NONE:
- case DB_INT:
- case DB_FLOAT:
- case DB_DOUBLE:
- case DB_BITMAP:
- /* No need to do anything for these types */
- break;
- }
- }
- /**
- * Update values of MySQL bound parameters with values from
- * the DB API.
- * @param cmd Command structure which contains pointers to MYSQL_STMT and parameters values
- * @see bind_mysql_params
- */
- static inline void set_mysql_params(db_cmd_t* cmd)
- {
- struct my_cmd* mcmd;
- int i;
- mcmd = DB_GET_PAYLOAD(cmd);
- /* FIXME: We are updating internals of the prepared statement here,
- * this is probably not nice but I could not find another way of
- * updating the pointer to the buffer without the need to run
- * mysql_stmt_bind_param again (which would be innefficient)
- */
- for(i = 0; i < cmd->vals_count; i++) {
- update_field(mcmd->st->params + i, cmd->vals + i);
- }
- for(i = 0; i < cmd->match_count; i++) {
- update_field(mcmd->st->params + cmd->vals_count + i, cmd->match + i);
- }
- }
- static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
- {
- int i;
- struct my_fld* rp; /* Payload of the current field in result */
- struct tm t;
- /* Iterate through all the fields returned by MySQL and convert
- * them to DB API representation if necessary
- */
- for(i = 0; i < st->field_count; i++) {
- rp = DB_GET_PAYLOAD(result + i);
- if (rp->is_null) {
- result[i].flags |= DB_NULL;
- continue;
- } else {
- result[i].flags &= ~DB_NULL;
- }
- switch(result[i].type) {
- case DB_STR:
- result[i].v.lstr.len = rp->length;
- #ifdef MYSQL_FAKE_NULL
- if (STR_EQ(FAKE_NULL_STR,result[i].v.lstr)) {
- result[i].flags |= DB_NULL;
- }
- #endif
- break;
- case DB_BLOB:
- result[i].v.blob.len = rp->length;
- break;
- case DB_CSTR:
- if (rp->length < STR_BUF_SIZE) {
- result[i].v.cstr[rp->length] = '\0';
- } else {
- /* Truncated field but rp->length contains full size,
- * zero terminated the last byte in the buffer
- */
- result[i].v.cstr[STR_BUF_SIZE - 1] = '\0';
- }
- #ifdef MYSQL_FAKE_NULL
- if (strcmp(FAKE_NULL_STR.s,result[i].v.cstr)==0) {
- result[i].flags |= DB_NULL;
- }
- #endif
- break;
-
- case DB_DATETIME:
- memset(&t, '\0', sizeof(struct tm));
- t.tm_sec = rp->time.second;
- t.tm_min = rp->time.minute;
- t.tm_hour = rp->time.hour;
- t.tm_mday = rp->time.day;
- t.tm_mon = rp->time.month - 1;
- t.tm_year = rp->time.year - 1900;
- /* Daylight saving information got lost in the database
- * so let timegm to guess it. This eliminates the bug when
- * contacts reloaded from the database have different time
- * of expiration by one hour when daylight saving is used
- */
- t.tm_isdst = -1;
- #ifdef HAVE_TIMEGM
- result[i].v.time = timegm(&t);
- #else
- result[i].v.time = _timegm(&t);
- #endif /* HAVE_TIMEGM */
- break;
- case DB_INT:
- #ifdef MYSQL_FAKE_NULL
- if (FAKE_NULL_INT==result[i].v.int4) {
- result[i].flags |= DB_NULL;
- }
- break;
- #endif
- case DB_NONE:
- case DB_FLOAT:
- case DB_DOUBLE:
- case DB_BITMAP:
- /* No need to do anything for these types */
- break;
- }
- }
-
- return 0;
- }
- /**
- * This is the main command execution function. The function contains
- * all the necessary logic to detect reset or disconnected database
- * connections and uploads commands to the server if necessary.
- * @param cmd Command to be executed
- * @return 0 if OK, <0 on MySQL failure, >0 on DB API failure
- */
- static int exec_cmd_safe(db_cmd_t* cmd)
- {
- int i, err;
- db_con_t* con;
- struct my_cmd* mcmd;
- struct my_con* mcon;
-
- /* First things first: retrieve connection info
- * from the currently active connection and also
- * mysql payload from the database command
- */
- mcmd = DB_GET_PAYLOAD(cmd);
- con = cmd->ctx->con[db_payload_idx];
- mcon = DB_GET_PAYLOAD(con);
-
- for(i = 0; i <= my_retries; i++) {
- if ((mcon->flags & MY_CONNECTED) == 0) {
- /* The connection is disconnected, try to reconnect */
- if (my_con_connect(con)) {
- INFO("mysql: exec_cmd_safe failed to re-connect\n");
- continue;
- }
- }
-
- /* Next check the number of resets in the database connection, if this
- * number is higher than the number we keep in my_cmd structure in
- * last_reset variable then the connection was reset and we need to
- * upload the command again to the server before executing it, because
- * the server recycles all server side information upon disconnect.
- */
- if (mcon->resets > mcmd->last_reset) {
- INFO("mysql: Connection reset detected, uploading command to server\n");
- err = upload_cmd(cmd);
- if (err < 0) {
- INFO("mysql: Error while uploading command\n");
- continue;
- } else if (err > 0) {
- /* DB API error, this is a serious problem such as memory
- * allocation failure, bail out
- */
- return 1;
- }
- }
-
- set_mysql_params(cmd);
- err = mysql_stmt_execute(mcmd->st);
- if (err == 0) {
- /* The command was executed successfully, now fetch all data to
- * the client if it was requested by the user */
- if (mcmd->flags & MY_FETCH_ALL) {
- err = mysql_stmt_store_result(mcmd->st);
- if (err) {
- INFO("mysql: Error while fetching data to client.\n");
- goto error;
- }
- }
- return 0;
- }
-
- error:
- /* Command execution failed, log a message and try to reconnect */
- INFO("mysql: libmysql: %d, %s\n", mysql_stmt_errno(mcmd->st),
- mysql_stmt_error(mcmd->st));
- INFO("mysql: Error while executing command on server, trying to reconnect\n");
- my_con_disconnect(con);
- if (my_con_connect(con)) {
- INFO("mysql: Failed to reconnect server\n");
- } else {
- INFO("mysql: Successfully reconnected server\n");
- }
- }
-
- INFO("mysql: Failed to execute command, giving up\n");
- return -1;
- }
- int my_cmd_exec(db_res_t* res, db_cmd_t* cmd)
- {
- struct my_cmd* mcmd;
- mcmd = DB_GET_PAYLOAD(cmd);
- mcmd->next_flag = -1;
- return exec_cmd_safe(cmd);
- }
- /**
- * Set MYSQL_BIND item.
- * @param bind destination
- * @param fld source
- */
- static void set_field(MYSQL_BIND *bind, db_fld_t* fld)
- {
- struct my_fld* f;
-
- f = DB_GET_PAYLOAD(fld);
- bind->is_null = &f->is_null;
- /* We can do it for all the types here, mysql will ignore it
- * for fixed-size types such as MYSQL_TYPE_LONG
- */
- bind->length = &f->length;
- switch(fld->type) {
- case DB_INT:
- case DB_BITMAP:
- bind->buffer_type = MYSQL_TYPE_LONG;
- bind->buffer = &fld->v.int4;
- break;
-
- case DB_FLOAT:
- bind->buffer_type = MYSQL_TYPE_FLOAT;
- bind->buffer = &fld->v.flt;
- break;
-
- case DB_DOUBLE:
- bind->buffer_type = MYSQL_TYPE_DOUBLE;
- bind->buffer = &fld->v.dbl;
- break;
-
- case DB_DATETIME:
- bind->buffer_type = MYSQL_TYPE_DATETIME;
- bind->buffer = &f->time;
- break;
-
- case DB_STR:
- case DB_CSTR:
- bind->buffer_type = MYSQL_TYPE_VAR_STRING;
- bind->buffer = ""; /* Updated on runtime */
- break;
-
- case DB_BLOB:
- bind->buffer_type = MYSQL_TYPE_BLOB;
- bind->buffer = ""; /* Updated on runtime */
- break;
-
- case DB_NONE:
- /* Eliminates gcc warning */
- break;
-
- }
- }
- /**
- * Bind params, give real values into prepared statement.
- * Up to two sets of parameters are provided.
- * Both of them are used in UPDATE command, params1 as colspecs and values and
- * params2 as WHERE clause. In other cases one set could be enough because values
- * or match (WHERE clause) is needed.
- * @param st MySQL command statement
- * @param params1 first set of params
- * @param params2 second set of params
- * @return 0 if OK, <0 on MySQL error, >0 on DB API error
- * @see update_params
- */
- static int bind_mysql_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* params2)
- {
- int my_idx, fld_idx;
- int count1, count2;
- MYSQL_BIND* my_params;
- int err = 0;
- /* Calculate the number of parameters */
- for(count1 = 0; !DB_FLD_EMPTY(params1) && !DB_FLD_LAST(params1[count1]); count1++);
- for(count2 = 0; !DB_FLD_EMPTY(params2) && !DB_FLD_LAST(params2[count2]); count2++);
- if (st->param_count != count1 + count2) {
- BUG("mysql: Number of parameters in SQL command does not match number of DB API parameters\n");
- return 1;
- }
-
- my_params = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * (count1 + count2));
- if (my_params == NULL) {
- ERR("mysql: No memory left\n");
- return -1;
- }
- memset(my_params, '\0', sizeof(MYSQL_BIND) * (count1 + count2));
- /* params1 */
- my_idx = 0;
- for (fld_idx = 0; fld_idx < count1; fld_idx++, my_idx++) {
- set_field(&my_params[my_idx], params1 + fld_idx);
- }
- /* params2 */
- for (fld_idx = 0; fld_idx < count2; fld_idx++, my_idx++) {
- set_field(&my_params[my_idx], params2 + fld_idx);
- }
- err = mysql_stmt_bind_param(st, my_params);
- if (err) {
- ERR("mysql: libmysqlclient: %d, %s\n",
- mysql_stmt_errno(st), mysql_stmt_error(st));
- goto error;
- }
- /* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
- * creates a copy in the statement and we will update it there
- */
- pkg_free(my_params);
- return err;
-
- error:
- if (my_params) pkg_free(my_params);
- return err;
- }
- /*
- * FIXME: This function will only work if we have one db connection
- * in every context, otherwise it would initialize the result set
- * from the first connection in the context.
- */
- static int check_result(db_cmd_t* cmd, struct my_cmd* payload)
- {
- int i, n;
- MYSQL_FIELD *fld;
- MYSQL_RES *meta = NULL;
- meta = mysql_stmt_result_metadata(payload->st);
- if (meta == NULL) {
- /* No error means no result set to be checked */
- if (mysql_stmt_errno(payload->st) == 0) return 0;
- ERR("mysql: Error while getting metadata of SQL command: %d, %s\n",
- mysql_stmt_errno(payload->st), mysql_stmt_error(payload->st));
- return -1;
- }
- n = mysql_num_fields(meta);
- if (cmd->result == NULL) {
- /* The result set parameter of db_cmd function was empty, that
- * means the command is select * and we have to create the array
- * of result fields in the cmd structure manually.
- */
- cmd->result = db_fld(n + 1);
- cmd->result_count = n;
- for(i = 0; i < cmd->result_count; i++) {
- struct my_fld *f;
- if (my_fld(cmd->result + i, cmd->table.s) < 0) goto error;
- f = DB_GET_PAYLOAD(cmd->result + i);
- fld = mysql_fetch_field_direct(meta, i);
- f->name = pkg_malloc(strlen(fld->name)+1);
- if (f->name == NULL) {
- ERR("mysql: Out of private memory\n");
- goto error;
- }
- strcpy(f->name, fld->name);
- cmd->result[i].name = f->name;
- }
- } else {
- if (cmd->result_count != n) {
- BUG("mysql: Number of fields in MySQL result does not match number of parameters in DB API\n");
- goto error;
- }
- }
- /* Now iterate through all the columns in the result set and replace
- * any occurrence of DB_UNKNOWN type with the type of the column
- * retrieved from the database and if no column name was provided then
- * update it from the database as well.
- */
- for(i = 0; i < cmd->result_count; i++) {
- fld = mysql_fetch_field_direct(meta, i);
- if (cmd->result[i].type != DB_NONE) continue;
- switch(fld->type) {
- case MYSQL_TYPE_TINY:
- case MYSQL_TYPE_SHORT:
- case MYSQL_TYPE_INT24:
- case MYSQL_TYPE_LONG:
- cmd->result[i].type = DB_INT;
- break;
- case MYSQL_TYPE_FLOAT:
- cmd->result[i].type = DB_FLOAT;
- break;
- case MYSQL_TYPE_DOUBLE:
- cmd->result[i].type = DB_DOUBLE;
- break;
- case MYSQL_TYPE_TIMESTAMP:
- case MYSQL_TYPE_DATETIME:
- cmd->result[i].type = DB_DATETIME;
- break;
- case MYSQL_TYPE_STRING:
- case MYSQL_TYPE_VAR_STRING:
- cmd->result[i].type = DB_STR;
- break;
- default:
- ERR("mysql: Unsupported MySQL column type: %d, table: %s, column: %s\n",
- fld->type, cmd->table.s, fld->name);
- goto error;
- }
- }
-
- if (meta) mysql_free_result(meta);
- return 0;
- error:
- if (meta) mysql_free_result(meta);
- return 1;
- }
- /* FIXME: Add support for DB_NONE, in this case the function should determine
- * the type of the column in the database and set the field type appropriately.
- * This function must be called after check_result.
- */
- static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
- {
- int i, n, err = 0;
- struct my_fld* f;
- MYSQL_BIND* result;
- /* Calculate the number of fields in the result */
- for(n = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[n]); n++);
- /* Return immediately if there are no fields in the result set */
- if (n == 0) return 0;
- result = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * n);
- if (result == NULL) {
- ERR("mysql: No memory left\n");
- return 1;
- }
- memset(result, '\0', sizeof(MYSQL_BIND) * n);
-
- for(i = 0; i < n; i++) {
- f = DB_GET_PAYLOAD(fld + i);
- result[i].is_null = &f->is_null;
- /* We can do it for all the types here, mysql will ignore it
- * for fixed-size types such as MYSQL_TYPE_LONG
- */
- result[i].length = &f->length;
- switch(fld[i].type) {
- case DB_INT:
- case DB_BITMAP:
- result[i].buffer_type = MYSQL_TYPE_LONG;
- result[i].buffer = &fld[i].v.int4;
- break;
- case DB_FLOAT:
- result[i].buffer_type = MYSQL_TYPE_FLOAT;
- result[i].buffer = &fld[i].v.flt;
- break;
-
- case DB_DOUBLE:
- result[i].buffer_type = MYSQL_TYPE_DOUBLE;
- result[i].buffer = &fld[i].v.dbl;
- break;
- case DB_DATETIME:
- result[i].buffer_type = MYSQL_TYPE_DATETIME;
- result[i].buffer = &f->time;
- break;
- case DB_STR:
- result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
- if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
- if (f->buf.s == NULL) {
- ERR("mysql: No memory left\n");
- err = 1;
- goto error;
- }
- result[i].buffer = f->buf.s;
- fld[i].v.lstr.s = f->buf.s;
- result[i].buffer_length = STR_BUF_SIZE - 1;
- break;
- case DB_CSTR:
- result[i].buffer_type = MYSQL_TYPE_VAR_STRING;
- if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
- if (f->buf.s == NULL) {
- ERR("mysql: No memory left\n");
- err = 1;
- goto error;
- }
- result[i].buffer = f->buf.s;
- fld[i].v.cstr = f->buf.s;
- result[i].buffer_length = STR_BUF_SIZE - 1;
- break;
- case DB_BLOB:
- result[i].buffer_type = MYSQL_TYPE_BLOB;
- if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
- if (f->buf.s == NULL) {
- ERR("mysql: No memory left\n");
- err = 1;
- goto error;
- }
- result[i].buffer = f->buf.s;
- fld[i].v.blob.s = f->buf.s;
- result[i].buffer_length = STR_BUF_SIZE - 1;
- break;
- case DB_NONE:
- /* Eliminates gcc warning */
- break;
- }
- }
- err = mysql_stmt_bind_result(st, result);
- if (err) {
- ERR("mysql: Error while binding result: %s\n", mysql_stmt_error(st));
- goto error;
- }
- /* We do not need the array of MYSQL_BIND anymore, mysql_stmt_bind_param
- * creates a copy in the statement and we will update it there
- */
- if (result) pkg_free(result);
- return 0;
-
- error:
- if (result) pkg_free(result);
- return err;
- }
- /**
- * Upload database command to the server
- * @param cmd Command to be uploaded
- * @return 0 if OK, >0 on DB API errors, <0 on MySQL errors
- */
- static int upload_cmd(db_cmd_t* cmd)
- {
- struct my_cmd* res;
- struct my_con* mcon;
- int err = 0;
-
- res = DB_GET_PAYLOAD(cmd);
-
- /* FIXME: The function should take the connection as one of parameters */
- mcon = DB_GET_PAYLOAD(cmd->ctx->con[db_payload_idx]);
- /* Do not upload the command if the connection is not connected */
- if ((mcon->flags & MY_CONNECTED) == 0) {
- err = 1;
- goto error;
- }
- /* If there is a previous pre-compiled statement, close it first */
- if (res->st) mysql_stmt_close(res->st);
- res->st = NULL;
-
- /* Create a new pre-compiled statement data structure */
- res->st = mysql_stmt_init(mcon->con);
- if (res->st == NULL) {
- ERR("mysql: Error while creating new MySQL_STMT data structure (no memory left)\n");
- err = 1;
- goto error;
- }
-
- /* Try to upload the command to the server */
- if (mysql_stmt_prepare(res->st, res->sql_cmd.s, res->sql_cmd.len)) {
- err = mysql_stmt_errno(res->st);
- ERR("mysql: libmysql: %d, %s\n", err, mysql_stmt_error(res->st));
- ERR("mysql: An error occurred while uploading command to server\n");
- }
- if (err == CR_SERVER_LOST ||
- err == CR_SERVER_GONE_ERROR) {
- /* Connection to the server was lost, mark the connection as
- * disconnected. In this case mysql_stmt_prepare invalidates the
- * connection internally and calling another mysql function on that
- * connection would crash. To make sure that no other mysql function
- * gets called unless the connection is reconnected we disconnect it
- * explicitly here. This is a workaround for mysql bug #33384. */
- my_con_disconnect(cmd->ctx->con[db_payload_idx]);
- }
- if (err) {
- /* Report mysql error to the caller */
- err = -1;
- goto error;
- }
-
- err = bind_mysql_params(res->st, cmd->vals, cmd->match);
- if (err) goto error;
-
- if (cmd->type == DB_GET || cmd->type == DB_SQL) {
- err = check_result(cmd, res);
- if (err) goto error;
- err = bind_result(res->st, cmd->result);
- if (err) goto error;
- }
-
- res->last_reset = mcon->resets;
- return 0;
-
- error:
- if (res->st) {
- mysql_stmt_close(res->st);
- res->st = NULL;
- }
- return err;
- }
- int my_cmd(db_cmd_t* cmd)
- {
- struct my_cmd* res;
-
- res = (struct my_cmd*)pkg_malloc(sizeof(struct my_cmd));
- if (res == NULL) {
- ERR("mysql: No memory left\n");
- goto error;
- }
- memset(res, '\0', sizeof(struct my_cmd));
- /* Fetch all data to client at once by default */
- res->flags |= MY_FETCH_ALL;
- if (db_drv_init(&res->gen, my_cmd_free) < 0) goto error;
- switch(cmd->type) {
- case DB_PUT:
- if (DB_FLD_EMPTY(cmd->vals)) {
- BUG("mysql: No parameters provided for DB_PUT in context '%.*s'\n",
- cmd->ctx->id.len, ZSW(cmd->ctx->id.s));
- goto error;
- }
- if (build_replace_cmd(&res->sql_cmd, cmd) < 0) goto error;
- break;
- case DB_DEL:
- if (build_delete_cmd(&res->sql_cmd, cmd) < 0) goto error;
- break;
- case DB_GET:
- if (build_select_cmd(&res->sql_cmd, cmd) < 0) goto error;
- break;
- case DB_UPD:
- if (build_update_cmd(&res->sql_cmd, cmd) < 0) goto error;
- break;
- case DB_SQL:
- res->sql_cmd.s = (char*)pkg_malloc(cmd->table.len);
- if (res->sql_cmd.s == NULL) {
- ERR("mysql: Out of private memory\n");
- goto error;
- }
- memcpy(res->sql_cmd.s,cmd->table.s, cmd->table.len);
- res->sql_cmd.len = cmd->table.len;
- break;
- }
- DB_SET_PAYLOAD(cmd, res);
- /* In order to check all the parameters and results, we need to upload
- * the command to the server. We need to do that here before we report
- * back that the command was created successfully. Hence, this
- * function requires the corresponding connection be established. We
- * would not be able to check parameters if we don't do that there and
- * that could result in repeated execution failures at runtime.
- */
- if (upload_cmd(cmd)) goto error;
- return 0;
- error:
- if (res) {
- DB_SET_PAYLOAD(cmd, NULL);
- db_drv_free(&res->gen);
- if (res->sql_cmd.s) pkg_free(res->sql_cmd.s);
- pkg_free(res);
- }
- return -1;
- }
- int my_cmd_first(db_res_t* res) {
- struct my_cmd* mcmd;
- mcmd = DB_GET_PAYLOAD(res->cmd);
- switch (mcmd->next_flag) {
- case -2: /* table is empty */
- return 1;
- case 0: /* cursor position is 0 */
- return 0;
- case 1: /* next row */
- case 2: /* EOF */
- ERR("mysql: Unbuffered queries do not support cursor reset.\n");
- return -1;
- default:
- return my_cmd_next(res);
- }
- }
- int my_cmd_next(db_res_t* res)
- {
- int ret;
- struct my_cmd* mcmd;
- mcmd = DB_GET_PAYLOAD(res->cmd);
- if (mcmd->next_flag == 2 || mcmd->next_flag == -2) return 1;
- if (mcmd->st == NULL) {
- ERR("mysql: Prepared statement not found\n");
- return -1;
- }
- ret = mysql_stmt_fetch(mcmd->st);
-
- if (ret == MYSQL_NO_DATA) {
- mcmd->next_flag = mcmd->next_flag<0?-2:2;
- return 1;
- }
- /* MYSQL_DATA_TRUNCATED is only defined in mysql >= 5.0 */
- #if defined MYSQL_DATA_TRUNCATED
- if (ret == MYSQL_DATA_TRUNCATED) {
- int i;
- ERR("mysql: mysql_stmt_fetch, data truncated, fields: %d\n", res->cmd->result_count);
- for (i = 0; i < res->cmd->result_count; i++) {
- if (mcmd->st->bind[i].error /*&& mcmd->st->bind[i].buffer_length*/) {
- ERR("mysql: truncation, bind %d, length: %lu, buffer_length: %lu\n",
- i, *(mcmd->st->bind[i].length), mcmd->st->bind[i].buffer_length);
- }
- }
- ret = 0;
- }
- #endif
- if (mcmd->next_flag <= 0) {
- mcmd->next_flag++;
- }
- if (ret != 0) {
- ERR("mysql: Error in mysql_stmt_fetch (ret=%d): %s\n", ret, mysql_stmt_error(mcmd->st));
- return -1;
- }
- if (update_result(res->cmd->result, mcmd->st) < 0) {
- mysql_stmt_free_result(mcmd->st);
- return -1;
- }
- res->cur_rec->fld = res->cmd->result;
- return 0;
- }
- int my_getopt(db_cmd_t* cmd, char* optname, va_list ap)
- {
- struct my_cmd* mcmd;
- long long* id;
- int* val;
- mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
- if (!strcasecmp("last_id", optname)) {
- id = va_arg(ap, long long*);
- if (id == NULL) {
- BUG("mysql: NULL pointer passed to 'last_id' option\n");
- goto error;
- }
- if (mcmd->st->last_errno != 0) {
- BUG("mysql: Option 'last_id' called but previous command failed, "
- "check your code\n");
- return -1;
- }
- *id = mysql_stmt_insert_id(mcmd->st);
- if ((*id) == 0) {
- BUG("mysql: Option 'last_id' called but there is no auto-increment"
- " column in table, SQL command: %.*s\n", STR_FMT(&mcmd->sql_cmd));
- return -1;
- }
- } else if (!strcasecmp("fetch_all", optname)) {
- val = va_arg(ap, int*);
- if (val == NULL) {
- BUG("mysql: NULL pointer passed to 'fetch_all' DB option\n");
- goto error;
- }
- *val = mcmd->flags;
- } else {
- return 1;
- }
- return 0;
- error:
- return -1;
- }
- int my_setopt(db_cmd_t* cmd, char* optname, va_list ap)
- {
- struct my_cmd* mcmd;
- int* val;
- mcmd = (struct my_cmd*)DB_GET_PAYLOAD(cmd);
- if (!strcasecmp("fetch_all", optname)) {
- val = va_arg(ap, int*);
- if (val != 0) {
- mcmd->flags |= MY_FETCH_ALL;
- } else {
- mcmd->flags &= ~MY_FETCH_ALL;
- }
- } else {
- return 1;
- }
- return 0;
- }
- /** @} */
|