[cosmetics] update date in GPL header
[vuplus_xbmc] / xbmc / dbwrappers / mysqldataset.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include <iostream>
22 #include <string>
23 #include <set>
24
25 #include "utils/log.h"
26 #include "system.h" // for GetLastError()
27
28 #ifdef HAS_MYSQL
29 #include "mysqldataset.h"
30 #include "mysql/errmsg.h"
31 #ifdef _WIN32
32 #pragma comment(lib, "mysqlclient.lib")
33 #endif
34
35
36 #define MYSQL_OK          0
37 #define ER_BAD_DB_ERROR   1049
38
39 using namespace std;
40
41 namespace dbiplus {
42
43 //************* MysqlDatabase implementation ***************
44
45 MysqlDatabase::MysqlDatabase() {
46
47   active = false;
48   _in_transaction = false;     // for transaction
49
50   error = "Unknown database error";//S_NO_CONNECTION;
51   host = "localhost";
52   port = "3306";
53   db = "mysql";
54   login = "root";
55   passwd = "null";
56   conn = NULL;
57   default_charset = "";
58 }
59
60 MysqlDatabase::~MysqlDatabase() {
61   disconnect();
62 }
63
64 Dataset* MysqlDatabase::CreateDataset() const {
65    return new MysqlDataset((MysqlDatabase*)this);
66 }
67
68 int MysqlDatabase::status(void) {
69   if (active == false) return DB_CONNECTION_NONE;
70   return DB_CONNECTION_OK;
71 }
72
73 int MysqlDatabase::setErr(int err_code, const char * qry) {
74   switch (err_code)
75   {
76     case MYSQL_OK:
77       error = "Successful result";
78       break;
79     case CR_COMMANDS_OUT_OF_SYNC:
80       error = "Commands were executed in an improper order";
81       break;
82     case CR_SERVER_GONE_ERROR:
83       error = "The MySQL server has gone away";
84       break;
85     case CR_SERVER_LOST:
86       error = "The connection to the server was lost during this query";
87       break;
88     case CR_UNKNOWN_ERROR:
89       error = "An unknown error occurred";
90       break;
91     case 1146: /* ER_NO_SUCH_TABLE */
92       error = "The table does not exist";
93       break;
94     default:
95       char err[256];
96       snprintf(err, 256, "Undefined MySQL error: Code (%d)", err_code);
97       error = err;
98   }
99   error += "\nQuery: ";
100   error += qry;
101   error += "\n";
102   return err_code;
103 }
104
105 const char *MysqlDatabase::getErrorMsg() {
106    return error.c_str();
107 }
108
109 int MysqlDatabase::connect(bool create_new) {
110   if (host.empty() || db.empty())
111     return DB_CONNECTION_NONE;
112
113   //CLog::Log(LOGDEBUG, "Connecting to mysql:%s:%s", host.c_str(), db.c_str());
114
115   try
116   {
117     disconnect();
118
119     if (conn == NULL)
120       conn = mysql_init(conn);
121
122     // establish connection with just user credentials
123     if (mysql_real_connect(conn, host.c_str(),login.c_str(),passwd.c_str(), NULL, atoi(port.c_str()),NULL,0) != NULL)
124     {
125       // disable mysql autocommit since we handle it
126       //mysql_autocommit(conn, false);
127
128       // enforce utf8 charset usage
129       default_charset = mysql_character_set_name(conn);
130       if(mysql_set_character_set(conn, "utf8")) // returns 0 on success
131       {
132         CLog::Log(LOGERROR, "Unable to set utf8 charset: %s [%d](%s)",
133                   db.c_str(), mysql_errno(conn), mysql_error(conn));
134       }
135
136       // check existence
137       if (exists())
138       {
139         // nothing to see here
140       }
141       else if (create_new)
142       {
143         char sqlcmd[512];
144         int ret;
145
146         sprintf(sqlcmd, "CREATE DATABASE `%s`", db.c_str());
147         if ( (ret=query_with_reconnect(sqlcmd)) != MYSQL_OK )
148         {
149           throw DbErrors("Can't create new database: '%s' (%d)", db.c_str(), ret);
150         }
151       }
152
153       if (mysql_select_db(conn, db.c_str()) == 0)
154       {
155         active = true;
156         return DB_CONNECTION_OK;
157       }
158     }
159
160     // if we failed above, either credentials were incorrect or the database didn't exist
161     if (mysql_errno(conn) == ER_BAD_DB_ERROR && create_new)
162     {
163
164       if (create() == MYSQL_OK)
165       {
166         active = true;
167
168         return DB_CONNECTION_OK;
169       }
170     }
171
172     CLog::Log(LOGERROR, "Unable to open database: %s [%d](%s)",
173               db.c_str(), mysql_errno(conn), mysql_error(conn));
174
175     return DB_CONNECTION_NONE;
176   }
177   catch(...)
178   {
179     CLog::Log(LOGERROR, "Unable to open database: %s (%u)",
180               db.c_str(), GetLastError());
181   }
182   return DB_CONNECTION_NONE;
183 }
184
185 void MysqlDatabase::disconnect(void) {
186   if (conn != NULL)
187   {
188     mysql_close(conn);
189     conn = NULL;
190   }
191
192   active = false;
193 }
194
195 int MysqlDatabase::create() {
196   return connect(true);
197 }
198
199 int MysqlDatabase::drop() {
200   if (!active)
201     throw DbErrors("Can't drop database: no active connection...");
202   char sqlcmd[512];
203   int ret;
204   sprintf(sqlcmd,"DROP DATABASE `%s`", db.c_str());
205   if ( (ret=query_with_reconnect(sqlcmd)) != MYSQL_OK )
206   {
207     throw DbErrors("Can't drop database: '%s' (%d)", db.c_str(), ret);
208   }
209   disconnect();
210   return DB_COMMAND_OK;
211 }
212
213 int MysqlDatabase::copy(const char *backup_name) {
214   if ( !active || conn == NULL)
215     throw DbErrors("Can't copy database: no active connection...");
216
217   char sql[4096];
218   int ret;
219
220   // ensure we're connected to the db we are about to copy
221   if ( (ret=mysql_select_db(conn, db.c_str())) != MYSQL_OK )
222     throw DbErrors("Can't connect to source database: '%s'",db.c_str());
223
224   // grab a list of base tables only (no views)
225   sprintf(sql, "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
226   if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
227     throw DbErrors("Can't determine base tables for copy.");
228
229   // get list of all tables from old DB
230   MYSQL_RES* res = mysql_store_result(conn);
231
232   if (res)
233   {
234     if (mysql_num_rows(res) == 0)
235     {
236       mysql_free_result(res);
237       throw DbErrors("The source database was unexpectedly empty.");
238     }
239
240     // create the new database
241     sprintf(sql, "CREATE DATABASE `%s`", backup_name);
242     if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
243     {
244       mysql_free_result(res);
245       throw DbErrors("Can't create database for copy: '%s' (%d)", db.c_str(), ret);
246     }
247
248     MYSQL_ROW row;
249
250     // duplicate each table from old db to new db
251     while ( (row=mysql_fetch_row(res)) != NULL )
252     {
253       // copy the table definition
254       sprintf(sql, "CREATE TABLE %s.%s LIKE %s",
255               backup_name, row[0], row[0]);
256
257       if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
258       {
259         mysql_free_result(res);
260         throw DbErrors("Can't copy schema for table '%s'\nError: %s", db.c_str(), ret);
261       }
262
263       // copy the table data
264       sprintf(sql, "INSERT INTO %s.%s SELECT * FROM %s",
265               backup_name, row[0], row[0]);
266
267       if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
268       {
269         mysql_free_result(res);
270         throw DbErrors("Can't copy data for table '%s'\nError: %s", row[0], ret);
271       }
272     }
273     mysql_free_result(res);
274
275     // after table are recreated and repopulated we can recreate views
276     // grab a list of views and their definitions
277     sprintf(sql, "SELECT TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s'", db.c_str());
278     if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
279       throw DbErrors("Can't determine views to recreate.");
280
281     // get list of all views from old DB
282     MYSQL_RES* resViews = mysql_store_result(conn);
283
284     if (resViews)
285     {
286       while ( (row=mysql_fetch_row(resViews)) != NULL )
287       {
288         sprintf(sql, "CREATE VIEW %s.%s AS %s",
289                 backup_name, row[0], row[1]);
290
291         if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
292         {
293           mysql_free_result(resViews);
294           throw DbErrors("Can't create view '%s'\nError: %s", db.c_str(), ret);
295         }
296       }
297       mysql_free_result(resViews);
298     }
299   }
300
301   return 1;
302 }
303
304 int MysqlDatabase::query_with_reconnect(const char* query) {
305   int attempts = 5;
306   int result;
307
308   // try to reconnect if server is gone
309   while ( ((result = mysql_real_query(conn, query, strlen(query))) != MYSQL_OK) &&
310           ((result = mysql_errno(conn)) == CR_SERVER_GONE_ERROR || result == CR_SERVER_LOST) &&
311           (attempts-- > 0) )
312   {
313     CLog::Log(LOGINFO,"MYSQL server has gone. Will try %d more attempt(s) to reconnect.", attempts);
314     active = false;
315     connect(true);
316   }
317
318   return result;
319 }
320
321 long MysqlDatabase::nextid(const char* sname) {
322   CLog::Log(LOGDEBUG,"MysqlDatabase::nextid for %s",sname);
323   if (!active) return DB_UNEXPECTED_RESULT;
324   const char* seq_table = "sys_seq";
325   int id;/*,nrow,ncol;*/
326   MYSQL_RES* res;
327   char sqlcmd[512];
328   sprintf(sqlcmd,"select nextid from %s where seq_name = '%s'",seq_table, sname);
329   CLog::Log(LOGDEBUG,"MysqlDatabase::nextid will request");
330   if ((last_err = query_with_reconnect(sqlcmd)) != 0)
331   {
332     return DB_UNEXPECTED_RESULT;
333   }
334   res = mysql_store_result(conn);
335   if (mysql_num_rows(res) == 0)
336   {
337     id = 1;
338     sprintf(sqlcmd,"insert into %s (nextid,seq_name) values (%d,'%s')",seq_table,id,sname);
339     mysql_free_result(res);
340     if ((last_err = query_with_reconnect(sqlcmd)) != 0) return DB_UNEXPECTED_RESULT;
341     return id;
342   }
343   else
344   {
345     MYSQL_ROW row = mysql_fetch_row(res);
346     //id = (int)row[0];
347     id = -1;
348     unsigned long *lengths;
349     lengths = mysql_fetch_lengths(res);
350     CLog::Log(LOGINFO,"Next id is [%.*s] ", (int) lengths[0], row[0]);
351     sprintf(sqlcmd,"update %s set nextid=%d where seq_name = '%s'",seq_table,id,sname);
352     mysql_free_result(res);
353     if ((last_err = query_with_reconnect(sqlcmd) != 0)) return DB_UNEXPECTED_RESULT;
354     return id;
355   }
356   return DB_UNEXPECTED_RESULT;
357 }
358
359 // methods for transactions
360 // ---------------------------------------------
361 void MysqlDatabase::start_transaction() {
362   if (active)
363   {
364     CLog::Log(LOGDEBUG,"Mysql Start transaction");
365     _in_transaction = true;
366   }
367 }
368
369 void MysqlDatabase::commit_transaction() {
370   if (active)
371   {
372     mysql_commit(conn);
373     CLog::Log(LOGDEBUG,"Mysql commit transaction");
374     _in_transaction = false;
375   }
376 }
377
378 void MysqlDatabase::rollback_transaction() {
379   if (active)
380   {
381     mysql_rollback(conn);
382     CLog::Log(LOGDEBUG,"Mysql rollback transaction");
383     _in_transaction = false;
384   }
385 }
386
387 bool MysqlDatabase::exists(void) {
388   bool ret = false;
389
390   if ( conn == NULL || mysql_ping(conn) )
391   {
392     CLog::Log(LOGERROR, "Not connected to database, test of existence is not possible.");
393     return ret;
394   }
395
396   MYSQL_RES* result = mysql_list_dbs(conn, db.c_str());
397   if (result == NULL)
398   {
399     CLog::Log(LOGERROR,"Database is not present, does the user has CREATE DATABASE permission");
400     return false;
401   }
402
403   ret = (mysql_num_rows(result) > 0);
404   mysql_free_result(result);
405
406   // Check if there is some tables ( to permit user with no create database rights
407   if (ret)
408   {
409     result = mysql_list_tables(conn, NULL);
410     if (result != NULL)
411       ret = (mysql_num_rows(result) > 0);
412
413     mysql_free_result(result);
414   }
415
416   return ret;
417 }
418
419 // methods for formatting
420 // ---------------------------------------------
421 string MysqlDatabase::vprepare(const char *format, va_list args)
422 {
423   string strFormat = format;
424   string strResult = "";
425   char *p;
426   size_t pos;
427
428   //  %q is the sqlite format string for %s.
429   //  Any bad character, like "'", will be replaced with a proper one
430   pos = 0;
431   while ( (pos = strFormat.find("%s", pos)) != string::npos )
432     strFormat.replace(pos++, 2, "%q");
433
434   p = mysql_vmprintf(strFormat.c_str(), args);
435   if ( p )
436   {
437     strResult = p;
438     free(p);
439
440     //  RAND() is the mysql form of RANDOM()
441     pos = 0;
442     while ( (pos = strResult.find("RANDOM()", pos)) != string::npos )
443     {
444       strResult.replace(pos++, 8, "RAND()");
445       pos += 6;
446     }
447   }
448
449   return strResult;
450 }
451
452 /* vsprintf() functionality is based on sqlite3.c functions */
453
454 /*
455 ** Conversion types fall into various categories as defined by the
456 ** following enumeration.
457 */
458 #define etRADIX       1 /* Integer types.  %d, %x, %o, and so forth */
459 #define etFLOAT       2 /* Floating point.  %f */
460 #define etEXP         3 /* Exponentional notation. %e and %E */
461 #define etGENERIC     4 /* Floating or exponential, depending on exponent. %g */
462 #define etSIZE        5 /* Return number of characters processed so far. %n */
463 #define etSTRING      6 /* Strings. %s */
464 #define etDYNSTRING   7 /* Dynamically allocated strings. %z */
465 #define etPERCENT     8 /* Percent symbol. %% */
466 #define etCHARX       9 /* Characters. %c */
467 /* The rest are extensions, not normally found in printf() */
468 #define etSQLESCAPE  10 /* Strings with '\'' doubled. Stings with '\\' escaped.  %q */
469 #define etSQLESCAPE2 11 /* Strings with '\'' doubled and enclosed in '',
470                           NULL pointers replaced by SQL NULL.  %Q */
471 #define etPOINTER    14 /* The %p conversion */
472 #define etSQLESCAPE3 15 /* %w -> Strings with '\"' doubled */
473
474 #define etINVALID     0 /* Any unrecognized conversion type */
475
476 #define ARRAY_SIZE(X)    ((int)(sizeof(X)/sizeof(X[0])))
477
478 /*
479 ** An "etByte" is an 8-bit unsigned value.
480 */
481 typedef unsigned char etByte;
482
483 /*
484 ** Each builtin conversion character (ex: the 'd' in "%d") is described
485 ** by an instance of the following structure
486 */
487 typedef struct et_info {   /* Information about each format field */
488   char fmttype;            /* The format field code letter */
489   etByte base;             /* The base for radix conversion */
490   etByte flags;            /* One or more of FLAG_ constants below */
491   etByte type;             /* Conversion paradigm */
492   etByte charset;          /* Offset into aDigits[] of the digits string */
493   etByte prefix;           /* Offset into aPrefix[] of the prefix string */
494 } et_info;
495
496 /*
497 ** An objected used to accumulate the text of a string where we
498 ** do not necessarily know how big the string will be in the end.
499 */
500 struct StrAccum {
501   char *zBase;         /* A base allocation.  Not from malloc. */
502   char *zText;         /* The string collected so far */
503   int  nChar;          /* Length of the string so far */
504   int  nAlloc;         /* Amount of space allocated in zText */
505   int  mxAlloc;        /* Maximum allowed string length */
506   bool mallocFailed;   /* Becomes true if any memory allocation fails */
507   bool tooBig;         /* Becomes true if string size exceeds limits */
508 };
509
510 /*
511 ** Allowed values for et_info.flags
512 */
513 #define FLAG_SIGNED  1     /* True if the value to convert is signed */
514 #define FLAG_INTERN  2     /* True if for internal use only */
515 #define FLAG_STRING  4     /* Allow infinity precision */
516
517
518 /*
519 ** The following table is searched linearly, so it is good to put the
520 ** most frequently used conversion types first.
521 */
522 static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
523 static const char aPrefix[] = "-x0\000X0";
524 static const et_info fmtinfo[] = {
525   {  'd', 10, 1, etRADIX,      0,  0 },
526   {  's',  0, 4, etSTRING,     0,  0 },
527   {  'g',  0, 1, etGENERIC,    30, 0 },
528   {  'z',  0, 4, etDYNSTRING,  0,  0 },
529   {  'q',  0, 4, etSQLESCAPE,  0,  0 },
530   {  'Q',  0, 4, etSQLESCAPE2, 0,  0 },
531   {  'w',  0, 4, etSQLESCAPE3, 0,  0 },
532   {  'c',  0, 0, etCHARX,      0,  0 },
533   {  'o',  8, 0, etRADIX,      0,  2 },
534   {  'u', 10, 0, etRADIX,      0,  0 },
535   {  'x', 16, 0, etRADIX,      16, 1 },
536   {  'X', 16, 0, etRADIX,      0,  4 },
537   {  'f',  0, 1, etFLOAT,      0,  0 },
538   {  'e',  0, 1, etEXP,        30, 0 },
539   {  'E',  0, 1, etEXP,        14, 0 },
540   {  'G',  0, 1, etGENERIC,    14, 0 },
541   {  'i', 10, 1, etRADIX,      0,  0 },
542   {  'n',  0, 0, etSIZE,       0,  0 },
543   {  '%',  0, 0, etPERCENT,    0,  0 },
544   {  'p', 16, 0, etPOINTER,    0,  1 },
545 };
546
547 /*
548 ** "*val" is a double such that 0.1 <= *val < 10.0
549 ** Return the ascii code for the leading digit of *val, then
550 ** multiply "*val" by 10.0 to renormalize.
551 **
552 ** Example:
553 **     input:     *val = 3.14159
554 **     output:    *val = 1.4159    function return = '3'
555 **
556 ** The counter *cnt is incremented each time.  After counter exceeds
557 ** 16 (the number of significant digits in a 64-bit float) '0' is
558 ** always returned.
559 */
560 char MysqlDatabase::et_getdigit(double *val, int *cnt) {
561   int digit;
562   double d;
563   if( (*cnt)++ >= 16 ) return '0';
564   digit = (int)*val;
565   d = digit;
566   digit += '0';
567   *val = (*val - d)*10.0;
568   return (char)digit;
569 }
570
571 /*
572 ** Append N space characters to the given string buffer.
573 */
574 void MysqlDatabase::appendSpace(StrAccum *pAccum, int N) {
575   static const char zSpaces[] = "                             ";
576   while( N>=(int)sizeof(zSpaces)-1 ) {
577     mysqlStrAccumAppend(pAccum, zSpaces, sizeof(zSpaces)-1);
578     N -= sizeof(zSpaces)-1;
579   }
580   if( N>0 ){
581     mysqlStrAccumAppend(pAccum, zSpaces, N);
582   }
583 }
584
585 #ifndef MYSQL_PRINT_BUF_SIZE
586 # define MYSQL_PRINT_BUF_SIZE 350
587 #endif
588
589 #define etBUFSIZE MYSQL_PRINT_BUF_SIZE  /* Size of the output buffer */
590
591
592
593 /*
594 ** The maximum length of a TEXT or BLOB in bytes.   This also
595 ** limits the size of a row in a table or index.
596 **
597 ** The hard limit is the ability of a 32-bit signed integer
598 ** to count the size: 2^31-1 or 2147483647.
599 */
600 #ifndef MYSQL_MAX_LENGTH
601 # define MYSQL_MAX_LENGTH 1000000000
602 #endif
603
604 /*
605 ** The root program.  All variations call this core.
606 **
607 ** INPUTS:
608 **   func   This is a pointer to a function taking three arguments
609 **            1. A pointer to anything.  Same as the "arg" parameter.
610 **            2. A pointer to the list of characters to be output
611 **               (Note, this list is NOT null terminated.)
612 **            3. An integer number of characters to be output.
613 **               (Note: This number might be zero.)
614 **
615 **   arg    This is the pointer to anything which will be passed as the
616 **          first argument to "func".  Use it for whatever you like.
617 **
618 **   fmt    This is the format string, as in the usual print.
619 **
620 **   ap     This is a pointer to a list of arguments.  Same as in
621 **          vfprint.
622 **
623 ** OUTPUTS:
624 **          The return value is the total number of characters sent to
625 **          the function "func".  Returns -1 on a error.
626 **
627 ** Note that the order in which automatic variables are declared below
628 ** seems to make a big difference in determining how fast this beast
629 ** will run.
630 */
631 void MysqlDatabase::mysqlVXPrintf(
632   StrAccum *pAccum,                  /* Accumulate results here */
633   int useExtended,                   /* Allow extended %-conversions */
634   const char *fmt,                   /* Format string */
635   va_list ap                         /* arguments */
636 ){
637   int c;                     /* Next character in the format string */
638   char *bufpt;               /* Pointer to the conversion buffer */
639   int precision;             /* Precision of the current field */
640   int length;                /* Length of the field */
641   int idx;                   /* A general purpose loop counter */
642   int width;                 /* Width of the current field */
643   etByte flag_leftjustify;   /* True if "-" flag is present */
644   etByte flag_plussign;      /* True if "+" flag is present */
645   etByte flag_blanksign;     /* True if " " flag is present */
646   etByte flag_alternateform; /* True if "#" flag is present */
647   etByte flag_altform2;      /* True if "!" flag is present */
648   etByte flag_zeropad;       /* True if field width constant starts with zero */
649   etByte flag_long;          /* True if "l" flag is present */
650   etByte flag_longlong;      /* True if the "ll" flag is present */
651   etByte done;               /* Loop termination flag */
652   uint64_t longvalue;        /* Value for integer types */
653   double realvalue;          /* Value for real types */
654   const et_info *infop;      /* Pointer to the appropriate info structure */
655   char buf[etBUFSIZE];       /* Conversion buffer */
656   char prefix;               /* Prefix character.  "+" or "-" or " " or '\0'. */
657   etByte xtype = 0;          /* Conversion paradigm */
658   char *zExtra;              /* Extra memory used for etTCLESCAPE conversions */
659   int  exp, e2;              /* exponent of real numbers */
660   double rounder;            /* Used for rounding floating point values */
661   etByte flag_dp;            /* True if decimal point should be shown */
662   etByte flag_rtz;           /* True if trailing zeros should be removed */
663   etByte flag_exp;           /* True to force display of the exponent */
664   int nsd;                   /* Number of significant digits returned */
665
666   length = 0;
667   bufpt = 0;
668   for(; (c=(*fmt))!=0; ++fmt){
669     if( c!='%' ){
670       int amt;
671       bufpt = (char *)fmt;
672       amt = 1;
673       while( (c=(*++fmt))!='%' && c!=0 ) amt++;
674       mysqlStrAccumAppend(pAccum, bufpt, amt);
675       if( c==0 ) break;
676     }
677     if( (c=(*++fmt))==0 ){
678       mysqlStrAccumAppend(pAccum, "%", 1);
679       break;
680     }
681     /* Find out what flags are present */
682     flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_altform2 = flag_zeropad = 0;
683     done = 0;
684     do
685     {
686       switch( c )
687       {
688         case '-':   flag_leftjustify = 1;     break;
689         case '+':   flag_plussign = 1;        break;
690         case ' ':   flag_blanksign = 1;       break;
691         case '#':   flag_alternateform = 1;   break;
692         case '!':   flag_altform2 = 1;        break;
693         case '0':   flag_zeropad = 1;         break;
694         default:    done = 1;                 break;
695       }
696     } while( !done && (c=(*++fmt))!=0 );
697     /* Get the field width */
698     width = 0;
699     if( c=='*' ){
700       width = va_arg(ap,int);
701       if( width<0 ){
702         flag_leftjustify = 1;
703         width = -width;
704       }
705       c = *++fmt;
706     }else{
707       while( c>='0' && c<='9' ){
708         width = width*10 + c - '0';
709         c = *++fmt;
710       }
711     }
712     if( width > etBUFSIZE-10 ){
713       width = etBUFSIZE-10;
714     }
715     /* Get the precision */
716     if( c=='.' ){
717       precision = 0;
718       c = *++fmt;
719       if( c=='*' ){
720         precision = va_arg(ap,int);
721         if( precision<0 ) precision = -precision;
722         c = *++fmt;
723       }else{
724         while( c>='0' && c<='9' ){
725           precision = precision*10 + c - '0';
726           c = *++fmt;
727         }
728       }
729     }else{
730       precision = -1;
731     }
732     /* Get the conversion type modifier */
733     if( c=='l' ){
734       flag_long = 1;
735       c = *++fmt;
736       if( c=='l' ){
737         flag_longlong = 1;
738         c = *++fmt;
739       }else{
740         flag_longlong = 0;
741       }
742     }else{
743       flag_long = flag_longlong = 0;
744     }
745     /* Fetch the info entry for the field */
746     infop = &fmtinfo[0];
747     xtype = etINVALID;
748     for(idx=0; idx<ARRAY_SIZE(fmtinfo); idx++){
749       if( c==fmtinfo[idx].fmttype ){
750         infop = &fmtinfo[idx];
751         if( useExtended || (infop->flags & FLAG_INTERN)==0 ){
752           xtype = infop->type;
753         }else{
754           return;
755         }
756         break;
757       }
758     }
759     zExtra = 0;
760
761
762     /* Limit the precision to prevent overflowing buf[] during conversion */
763     if( precision>etBUFSIZE-40 && (infop->flags & FLAG_STRING)==0 ){
764       precision = etBUFSIZE-40;
765     }
766
767     /*
768     ** At this point, variables are initialized as follows:
769     **
770     **   flag_alternateform          TRUE if a '#' is present.
771     **   flag_altform2               TRUE if a '!' is present.
772     **   flag_plussign               TRUE if a '+' is present.
773     **   flag_leftjustify            TRUE if a '-' is present or if the
774     **                               field width was negative.
775     **   flag_zeropad                TRUE if the width began with 0.
776     **   flag_long                   TRUE if the letter 'l' (ell) prefixed
777     **                               the conversion character.
778     **   flag_longlong               TRUE if the letter 'll' (ell ell) prefixed
779     **                               the conversion character.
780     **   flag_blanksign              TRUE if a ' ' is present.
781     **   width                       The specified field width.  This is
782     **                               always non-negative.  Zero is the default.
783     **   precision                   The specified precision.  The default
784     **                               is -1.
785     **   xtype                       The class of the conversion.
786     **   infop                       Pointer to the appropriate info struct.
787     */
788     switch( xtype ){
789       case etPOINTER:
790         flag_longlong = sizeof(char*)==sizeof(int64_t);
791         flag_long = sizeof(char*)==sizeof(long int);
792         /* Fall through into the next case */
793       case etRADIX:
794         if( infop->flags & FLAG_SIGNED ){
795           int64_t v;
796           if( flag_longlong ){
797             v = va_arg(ap,int64_t);
798           }else if( flag_long ){
799             v = va_arg(ap,long int);
800           }else{
801             v = va_arg(ap,int);
802           }
803           if( v<0 ){
804             longvalue = -v;
805             prefix = '-';
806           }else{
807             longvalue = v;
808             if( flag_plussign )        prefix = '+';
809             else if( flag_blanksign )  prefix = ' ';
810             else                       prefix = 0;
811           }
812         }else{
813           if( flag_longlong ){
814             longvalue = va_arg(ap,uint64_t);
815           }else if( flag_long ){
816             longvalue = va_arg(ap,unsigned long int);
817           }else{
818             longvalue = va_arg(ap,unsigned int);
819           }
820           prefix = 0;
821         }
822         if( longvalue==0 ) flag_alternateform = 0;
823         if( flag_zeropad && precision<width-(prefix!=0) ){
824           precision = width-(prefix!=0);
825         }
826         bufpt = &buf[etBUFSIZE-1];
827         {
828           register const char *cset;      /* Use registers for speed */
829           register int base;
830           cset = &aDigits[infop->charset];
831           base = infop->base;
832           do{                                           /* Convert to ascii */
833             *(--bufpt) = cset[longvalue%base];
834             longvalue = longvalue/base;
835           }while( longvalue>0 );
836         }
837         length = (int)(&buf[etBUFSIZE-1]-bufpt);
838         for(idx=precision-length; idx>0; idx--){
839           *(--bufpt) = '0';                             /* Zero pad */
840         }
841         if( prefix ) *(--bufpt) = prefix;               /* Add sign */
842         if( flag_alternateform && infop->prefix ){      /* Add "0" or "0x" */
843           const char *pre;
844           char x;
845           pre = &aPrefix[infop->prefix];
846           for(; (x=(*pre))!=0; pre++) *(--bufpt) = x;
847         }
848         length = (int)(&buf[etBUFSIZE-1]-bufpt);
849         bufpt[length] = 0;
850         break;
851       case etFLOAT:
852       case etEXP:
853       case etGENERIC:
854         realvalue = va_arg(ap,double);
855         if( precision<0 ) precision = 6;         /* Set default precision */
856         if( precision>etBUFSIZE/2-10 ) precision = etBUFSIZE/2-10;
857         if( realvalue<0.0 ){
858           realvalue = -realvalue;
859           prefix = '-';
860         }else{
861           if( flag_plussign )          prefix = '+';
862           else if( flag_blanksign )    prefix = ' ';
863           else                         prefix = 0;
864         }
865         if( xtype==etGENERIC && precision>0 ) precision--;
866         /* It makes more sense to use 0.5 */
867         for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1){}
868         if( xtype==etFLOAT ) realvalue += rounder;
869         /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
870         exp = 0;
871 #if 0
872         if( mysqlIsNaN((double)realvalue) ){
873           bufpt = "NaN";
874           length = 3;
875           break;
876         }
877 #endif
878         if( realvalue>0.0 ){
879           while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; }
880           while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
881           while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
882           while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; }
883           while( realvalue<1.0 ){ realvalue *= 10.0; exp--; }
884           if( exp>350 ){
885             if( prefix=='-' ){
886               bufpt = (char *)"-Inf";
887             }else if( prefix=='+' ){
888               bufpt = (char *)"+Inf";
889             }else{
890               bufpt = (char *)"Inf";
891             }
892             length = strlen(bufpt);
893             break;
894           }
895         }
896         bufpt = buf;
897         /*
898         ** If the field type is etGENERIC, then convert to either etEXP
899         ** or etFLOAT, as appropriate.
900         */
901         flag_exp = xtype==etEXP;
902         if( xtype!=etFLOAT ){
903           realvalue += rounder;
904           if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
905         }
906         if( xtype==etGENERIC ){
907           flag_rtz = !flag_alternateform;
908           if( exp<-4 || exp>precision ){
909             xtype = etEXP;
910           }else{
911             precision = precision - exp;
912             xtype = etFLOAT;
913           }
914         }else{
915           flag_rtz = 0;
916         }
917         if( xtype==etEXP ){
918           e2 = 0;
919         }else{
920           e2 = exp;
921         }
922         nsd = 0;
923         flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2;
924         /* The sign in front of the number */
925         if( prefix ){
926           *(bufpt++) = prefix;
927         }
928         /* Digits prior to the decimal point */
929         if( e2<0 ){
930           *(bufpt++) = '0';
931         }else{
932           for(; e2>=0; e2--){
933             *(bufpt++) = et_getdigit(&realvalue,&nsd);
934           }
935         }
936         /* The decimal point */
937         if( flag_dp ){
938           *(bufpt++) = '.';
939         }
940         /* "0" digits after the decimal point but before the first
941         ** significant digit of the number */
942         for(e2++; e2<0; precision--, e2++){
943           //ASSERT( precision>0 );
944           *(bufpt++) = '0';
945         }
946         /* Significant digits after the decimal point */
947         while( (precision--)>0 ){
948           *(bufpt++) = et_getdigit(&realvalue,&nsd);
949         }
950         /* Remove trailing zeros and the "." if no digits follow the "." */
951         if( flag_rtz && flag_dp ){
952           while( bufpt[-1]=='0' ) *(--bufpt) = 0;
953           //ASSERT( bufpt>buf );
954           if( bufpt[-1]=='.' ){
955             if( flag_altform2 ){
956               *(bufpt++) = '0';
957             }else{
958               *(--bufpt) = 0;
959             }
960           }
961         }
962         /* Add the "eNNN" suffix */
963         if( flag_exp || xtype==etEXP ){
964           *(bufpt++) = aDigits[infop->charset];
965           if( exp<0 ){
966             *(bufpt++) = '-'; exp = -exp;
967           }else{
968             *(bufpt++) = '+';
969           }
970           if( exp>=100 ){
971             *(bufpt++) = (char)((exp/100)+'0');        /* 100's digit */
972             exp %= 100;
973           }
974           *(bufpt++) = (char)(exp/10+'0');             /* 10's digit */
975           *(bufpt++) = (char)(exp%10+'0');             /* 1's digit */
976         }
977         *bufpt = 0;
978
979         /* The converted number is in buf[] and zero terminated. Output it.
980         ** Note that the number is in the usual order, not reversed as with
981         ** integer conversions. */
982         length = (int)(bufpt-buf);
983         bufpt = buf;
984
985         /* Special case:  Add leading zeros if the flag_zeropad flag is
986         ** set and we are not left justified */
987         if( flag_zeropad && !flag_leftjustify && length < width){
988           int i;
989           int nPad = width - length;
990           for(i=width; i>=nPad; i--){
991             bufpt[i] = bufpt[i-nPad];
992           }
993           i = prefix!=0;
994           while( nPad-- ) bufpt[i++] = '0';
995           length = width;
996         }
997         break;
998       case etSIZE:
999         *(va_arg(ap,int*)) = pAccum->nChar;
1000         length = width = 0;
1001         break;
1002       case etPERCENT:
1003         buf[0] = '%';
1004         bufpt = buf;
1005         length = 1;
1006         break;
1007       case etCHARX:
1008         c = va_arg(ap,int);
1009         buf[0] = (char)c;
1010         if( precision>=0 ){
1011           for(idx=1; idx<precision; idx++) buf[idx] = (char)c;
1012           length = precision;
1013         }else{
1014           length =1;
1015         }
1016         bufpt = buf;
1017         break;
1018       case etSTRING:
1019       case etDYNSTRING:
1020         bufpt = va_arg(ap,char*);
1021         if( bufpt==0 ){
1022           bufpt = (char *)"";
1023         }else if( xtype==etDYNSTRING ){
1024           zExtra = bufpt;
1025         }
1026         if( precision>=0 ){
1027           for(length=0; length<precision && bufpt[length]; length++){}
1028         }else{
1029           length = strlen(bufpt);
1030         }
1031         break;
1032       case etSQLESCAPE:
1033       case etSQLESCAPE2:
1034       case etSQLESCAPE3: {
1035         int i, j, k, n, isnull;
1036         int needQuote;
1037         char ch;
1038         char q = ((xtype==etSQLESCAPE3)?'"':'\'');   /* Quote character */
1039         const char *escarg = va_arg(ap, char*);
1040         isnull = escarg==0;
1041         if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
1042         k = precision;
1043         for(i=0; k!=0 && (ch=escarg[i])!=0; i++, k--);
1044         needQuote = !isnull && xtype==etSQLESCAPE2;
1045         n = i*2 + 1 + needQuote*2;
1046         if( n>etBUFSIZE ){
1047           bufpt = zExtra = (char *)malloc(n);
1048           if( bufpt==0 ){
1049             pAccum->mallocFailed = 1;
1050             return;
1051           }
1052         }else{
1053           bufpt = buf;
1054         }
1055         j = 0;
1056         if( needQuote ) bufpt[j++] = q;
1057         k = i;
1058         j += mysql_real_escape_string(conn, bufpt, escarg, k);
1059         if( needQuote ) bufpt[j++] = q;
1060         bufpt[j] = 0;
1061         length = j;
1062         /* The precision in %q and %Q means how many input characters to
1063         ** consume, not the length of the output...
1064         ** if( precision>=0 && precision<length ) length = precision; */
1065         break;
1066       }
1067       default: {
1068         return;
1069       }
1070     }/* End switch over the format type */
1071     /*
1072     ** The text of the conversion is pointed to by "bufpt" and is
1073     ** "length" characters long.  The field width is "width".  Do
1074     ** the output.
1075     */
1076     if( !flag_leftjustify ){
1077       register int nspace;
1078       nspace = width-length;
1079       if( nspace>0 ){
1080         appendSpace(pAccum, nspace);
1081       }
1082     }
1083     if( length>0 ){
1084       mysqlStrAccumAppend(pAccum, bufpt, length);
1085     }
1086     if( flag_leftjustify ){
1087       register int nspace;
1088       nspace = width-length;
1089       if( nspace>0 ){
1090         appendSpace(pAccum, nspace);
1091       }
1092     }
1093     if( zExtra ){
1094       free(zExtra);
1095     }
1096   }/* End for loop over the format string */
1097 } /* End of function */
1098
1099 /*
1100 ** Append N bytes of text from z to the StrAccum object.
1101 */
1102 void MysqlDatabase::mysqlStrAccumAppend(StrAccum *p, const char *z, int N) {
1103   if( p->tooBig | p->mallocFailed ){
1104     return;
1105   }
1106   if( N<0 ){
1107     N = strlen(z);
1108   }
1109   if( N==0 || z==0 ){
1110     return;
1111   }
1112   if( p->nChar+N >= p->nAlloc ){
1113     char *zNew;
1114     int szNew = p->nChar;
1115     szNew += N + 1;
1116     if( szNew > p->mxAlloc ){
1117       mysqlStrAccumReset(p);
1118       p->tooBig = 1;
1119       return;
1120     }else{
1121       p->nAlloc = szNew;
1122     }
1123     zNew = (char *)malloc(p->nAlloc);
1124     if( zNew ){
1125       memcpy(zNew, p->zText, p->nChar);
1126       mysqlStrAccumReset(p);
1127       p->zText = zNew;
1128     }else{
1129       p->mallocFailed = 1;
1130       mysqlStrAccumReset(p);
1131       return;
1132     }
1133   }
1134   memcpy(&p->zText[p->nChar], z, N);
1135   p->nChar += N;
1136 }
1137
1138 /*
1139 ** Finish off a string by making sure it is zero-terminated.
1140 ** Return a pointer to the resulting string.  Return a NULL
1141 ** pointer if any kind of error was encountered.
1142 */
1143 char * MysqlDatabase::mysqlStrAccumFinish(StrAccum *p){
1144   if( p->zText ){
1145     p->zText[p->nChar] = 0;
1146     if( p->zText==p->zBase ){
1147       p->zText = (char *)malloc(p->nChar+1);
1148       if( p->zText ){
1149         memcpy(p->zText, p->zBase, p->nChar+1);
1150       }else{
1151         p->mallocFailed = 1;
1152       }
1153     }
1154   }
1155   return p->zText;
1156 }
1157
1158 /*
1159 ** Reset an StrAccum string.  Reclaim all malloced memory.
1160 */
1161 void MysqlDatabase::mysqlStrAccumReset(StrAccum *p){
1162   if( p->zText!=p->zBase ){
1163     free(p->zText);
1164   }
1165   p->zText = 0;
1166 }
1167
1168 /*
1169 ** Initialize a string accumulator
1170 */
1171 void MysqlDatabase::mysqlStrAccumInit(StrAccum *p, char *zBase, int n, int mx){
1172   p->zText = p->zBase = zBase;
1173   p->nChar = 0;
1174   p->nAlloc = n;
1175   p->mxAlloc = mx;
1176   p->tooBig = 0;
1177   p->mallocFailed = 0;
1178 }
1179
1180 /*
1181 ** Print into memory obtained from mysql_malloc().  Omit the internal
1182 ** %-conversion extensions.
1183 */
1184 char *MysqlDatabase::mysql_vmprintf(const char *zFormat, va_list ap) {
1185   char *z;
1186   char zBase[MYSQL_PRINT_BUF_SIZE];
1187   StrAccum acc;
1188
1189   mysqlStrAccumInit(&acc, zBase, sizeof(zBase), MYSQL_MAX_LENGTH);
1190   mysqlVXPrintf(&acc, 0, zFormat, ap);
1191   z = mysqlStrAccumFinish(&acc);
1192   return z;
1193 }
1194
1195
1196 //************* MysqlDataset implementation ***************
1197
1198 MysqlDataset::MysqlDataset():Dataset() {
1199   haveError = false;
1200   db = NULL;
1201   errmsg = NULL;
1202   autorefresh = false;
1203 }
1204
1205
1206 MysqlDataset::MysqlDataset(MysqlDatabase *newDb):Dataset(newDb) {
1207   haveError = false;
1208   db = newDb;
1209   errmsg = NULL;
1210   autorefresh = false;
1211 }
1212
1213 MysqlDataset::~MysqlDataset() {
1214    if (errmsg) free(errmsg);
1215  }
1216
1217
1218 void MysqlDataset::set_autorefresh(bool val) {
1219     autorefresh = val;
1220 }
1221
1222
1223
1224 //--------- protected functions implementation -----------------//
1225
1226 MYSQL* MysqlDataset::handle(){
1227   if (db != NULL)
1228   {
1229     return static_cast<MysqlDatabase*>(db)->getHandle();
1230   }
1231
1232   return NULL;
1233 }
1234
1235 void MysqlDataset::make_query(StringList &_sql) {
1236   string query;
1237   int result = 0;
1238   if (db == NULL) throw DbErrors("No Database Connection");
1239   try
1240   {
1241     if (autocommit) db->start_transaction();
1242
1243     for (list<string>::iterator i =_sql.begin(); i!=_sql.end(); i++)
1244     {
1245       query = *i;
1246       Dataset::parse_sql(query);
1247       if ((result = static_cast<MysqlDatabase *>(db)->query_with_reconnect(query.c_str())) != MYSQL_OK)
1248       {
1249         throw DbErrors(db->getErrorMsg());
1250       }
1251     } // end of for
1252
1253     if (db->in_transaction() && autocommit) db->commit_transaction();
1254
1255     active = true;
1256     ds_state = dsSelect;
1257     if (autorefresh)
1258       refresh();
1259   } // end of try
1260   catch(...)
1261   {
1262     if (db->in_transaction()) db->rollback_transaction();
1263     throw;
1264   }
1265
1266 }
1267
1268 void MysqlDataset::make_insert() {
1269   make_query(insert_sql);
1270   last();
1271 }
1272
1273 void MysqlDataset::make_edit() {
1274   make_query(update_sql);
1275 }
1276
1277
1278 void MysqlDataset::make_deletion() {
1279   make_query(delete_sql);
1280 }
1281
1282 void MysqlDataset::fill_fields() {
1283   if ((db == NULL) || (result.record_header.size() == 0) || (result.records.size() < (unsigned int)frecno)) return;
1284
1285   if (fields_object->size() == 0) // Filling columns name
1286   {
1287     const unsigned int ncols = result.record_header.size();
1288     fields_object->resize(ncols);
1289     for (unsigned int i = 0; i < ncols; i++)
1290       (*fields_object)[i].props = result.record_header[i];
1291   }
1292
1293   //Filling result
1294   if (result.records.size() != 0)
1295   {
1296     const sql_record *row = result.records[frecno];
1297     if (row)
1298     {
1299       const unsigned int ncols = row->size();
1300       fields_object->resize(ncols);
1301       for (unsigned int i = 0; i < ncols; i++)
1302         (*fields_object)[i].val = row->at(i);
1303       return;
1304     }
1305   }
1306   const unsigned int ncols = result.record_header.size();
1307   fields_object->resize(ncols);
1308   for (unsigned int i = 0; i < ncols; i++)
1309     (*fields_object)[i].val = "";
1310 }
1311
1312
1313 //------------- public functions implementation -----------------//
1314 bool MysqlDataset::dropIndex(const char *table, const char *index)
1315 {
1316   string sql;
1317   string sql_prepared;
1318
1319   sql = "SELECT * FROM information_schema.statistics WHERE TABLE_SCHEMA=DATABASE() AND table_name='%s' AND index_name='%s'";
1320   sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
1321
1322   if (!query(sql_prepared))
1323     return false;
1324
1325   if (num_rows())
1326   {
1327     sql = "ALTER TABLE %s DROP INDEX %s";
1328     sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
1329
1330     if (exec(sql_prepared) != MYSQL_OK)
1331       return false;
1332   }
1333
1334   return true;
1335 }
1336
1337 static bool ci_test(char l, char r)
1338 {
1339   return tolower(l) == tolower(r);
1340 }
1341
1342 static size_t ci_find(const string& where, const string& what)
1343 {
1344   std::string::const_iterator loc = std::search(where.begin(), where.end(), what.begin(), what.end(), ci_test);
1345   if (loc == where.end())
1346     return string::npos;
1347   else
1348     return loc - where.begin();
1349 }
1350
1351 int MysqlDataset::exec(const string &sql) {
1352   if (!handle()) throw DbErrors("No Database Connection");
1353   string qry = sql;
1354   int res = 0;
1355   exec_res.clear();
1356
1357   // enforce the "auto_increment" keyword to be appended to "integer primary key"
1358   size_t loc;
1359
1360   if ( (loc=ci_find(qry, "integer primary key")) != string::npos)
1361   {
1362     qry = qry.insert(loc + 19, " auto_increment ");
1363   }
1364
1365   // force the charset and collation to UTF-8
1366   if ( ci_find(qry, "CREATE TABLE") != string::npos )
1367   {
1368     qry += " CHARACTER SET utf8 COLLATE utf8_general_ci";
1369   }
1370
1371   CLog::Log(LOGDEBUG,"Mysql execute: %s", qry.c_str());
1372
1373   if (db->setErr( static_cast<MysqlDatabase *>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) != MYSQL_OK)
1374   {
1375     throw DbErrors(db->getErrorMsg());
1376   }
1377   else
1378   {
1379     // TODO: collect results and store in exec_res
1380     return res;
1381   }
1382 }
1383
1384 int MysqlDataset::exec() {
1385    return exec(sql);
1386 }
1387
1388 const void* MysqlDataset::getExecRes() {
1389   return &exec_res;
1390 }
1391
1392
1393 bool MysqlDataset::query(const char *query) {
1394   if(!handle()) throw DbErrors("No Database Connection");
1395   std::string qry = query;
1396   int fs = qry.find("select");
1397   int fS = qry.find("SELECT");
1398   if (!( fs >= 0 || fS >=0))
1399     throw DbErrors("MUST be select SQL!");
1400
1401   close();
1402
1403   MYSQL_RES *stmt = NULL;
1404
1405   if ( static_cast<MysqlDatabase*>(db)->setErr(static_cast<MysqlDatabase*>(db)->query_with_reconnect(query), query) != MYSQL_OK )
1406     throw DbErrors(db->getErrorMsg());
1407
1408   MYSQL* conn = handle();
1409   stmt = mysql_store_result(conn);
1410
1411   // column headers
1412   const unsigned int numColumns = mysql_num_fields(stmt);
1413   MYSQL_FIELD *fields = mysql_fetch_fields(stmt);
1414   MYSQL_ROW row;
1415   result.record_header.resize(numColumns);
1416   for (unsigned int i = 0; i < numColumns; i++)
1417     result.record_header[i].name = fields[i].name;
1418
1419   // returned rows
1420   while ((row = mysql_fetch_row(stmt)))
1421   { // have a row of data
1422     sql_record *res = new sql_record;
1423     res->resize(numColumns);
1424     for (unsigned int i = 0; i < numColumns; i++)
1425     {
1426       field_value &v = res->at(i);
1427       switch (fields[i].type)
1428       {
1429         case MYSQL_TYPE_LONGLONG:
1430         case MYSQL_TYPE_DECIMAL:
1431         case MYSQL_TYPE_NEWDECIMAL:
1432         case MYSQL_TYPE_TINY:
1433         case MYSQL_TYPE_SHORT:
1434         case MYSQL_TYPE_INT24:
1435         case MYSQL_TYPE_LONG:
1436           if (row[i] != NULL)
1437           {
1438             v.set_asInt(atoi(row[i]));
1439           }
1440           else
1441           {
1442             v.set_asInt(0);
1443           }
1444           break;
1445         case MYSQL_TYPE_FLOAT:
1446         case MYSQL_TYPE_DOUBLE:
1447           if (row[i] != NULL)
1448           {
1449             v.set_asDouble(atof(row[i]));
1450           }
1451           else
1452           {
1453             v.set_asDouble(0);
1454           }
1455           break;
1456         case MYSQL_TYPE_STRING:
1457         case MYSQL_TYPE_VAR_STRING:
1458         case MYSQL_TYPE_VARCHAR:
1459           if (row[i] != NULL) v.set_asString((const char *)row[i] );
1460           break;
1461         case MYSQL_TYPE_TINY_BLOB:
1462         case MYSQL_TYPE_MEDIUM_BLOB:
1463         case MYSQL_TYPE_LONG_BLOB:
1464         case MYSQL_TYPE_BLOB:
1465           if (row[i] != NULL) v.set_asString((const char *)row[i]);
1466           break;
1467         case MYSQL_TYPE_NULL:
1468         default:
1469           CLog::Log(LOGDEBUG,"MYSQL: Unknown field type: %u", fields[i].type);
1470           v.set_asString("");
1471           v.set_isNull();
1472           break;
1473       }
1474     }
1475     result.records.push_back(res);
1476   }
1477   mysql_free_result(stmt);
1478   active = true;
1479   ds_state = dsSelect;
1480   this->first();
1481   return true;
1482 }
1483
1484 bool MysqlDataset::query(const string &q) {
1485   return query(q.c_str());
1486 }
1487
1488 void MysqlDataset::open(const string &sql) {
1489    set_select_sql(sql);
1490    open();
1491 }
1492
1493 void MysqlDataset::open() {
1494   if (select_sql.size())
1495   {
1496     query(select_sql.c_str());
1497   }
1498   else
1499   {
1500     ds_state = dsInactive;
1501   }
1502 }
1503
1504 void MysqlDataset::close() {
1505   Dataset::close();
1506   result.clear();
1507   edit_object->clear();
1508   fields_object->clear();
1509   ds_state = dsInactive;
1510   active = false;
1511 }
1512
1513
1514 void MysqlDataset::cancel() {
1515   if ((ds_state == dsInsert) || (ds_state==dsEdit))
1516   {
1517     if (result.record_header.size())
1518       ds_state = dsSelect;
1519     else
1520       ds_state = dsInactive;
1521   }
1522 }
1523
1524
1525 int MysqlDataset::num_rows() {
1526   return result.records.size();
1527 }
1528
1529
1530 bool MysqlDataset::eof() {
1531   return feof;
1532 }
1533
1534
1535 bool MysqlDataset::bof() {
1536   return fbof;
1537 }
1538
1539
1540 void MysqlDataset::first() {
1541   Dataset::first();
1542   this->fill_fields();
1543 }
1544
1545 void MysqlDataset::last() {
1546   Dataset::last();
1547   fill_fields();
1548 }
1549
1550 void MysqlDataset::prev(void) {
1551   Dataset::prev();
1552   fill_fields();
1553 }
1554
1555 void MysqlDataset::next(void) {
1556   Dataset::next();
1557   if (!eof())
1558       fill_fields();
1559 }
1560
1561 void MysqlDataset::free_row(void)
1562 {
1563   if (frecno < 0 || (unsigned int)frecno >= result.records.size())
1564     return;
1565
1566   sql_record *row = result.records[frecno];
1567   if (row)
1568   {
1569     delete row;
1570     result.records[frecno] = NULL;
1571   }
1572 }
1573
1574 bool MysqlDataset::seek(int pos) {
1575   if (ds_state == dsSelect)
1576   {
1577     Dataset::seek(pos);
1578     fill_fields();
1579     return true;
1580   }
1581
1582   return false;
1583 }
1584
1585 int64_t MysqlDataset::lastinsertid() {
1586   if (!handle()) DbErrors("No Database Connection");
1587   return mysql_insert_id(handle());
1588 }
1589
1590 long MysqlDataset::nextid(const char *seq_name) {
1591   if (handle())
1592     return db->nextid(seq_name);
1593
1594   return DB_UNEXPECTED_RESULT;
1595 }
1596
1597 void MysqlDataset::interrupt() {
1598   // Impossible
1599 }
1600
1601 }//namespace
1602 #endif //HAS_MYSQL
1603