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