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