MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
mythdbcon.cpp
Go to the documentation of this file.
1 #include <unistd.h>
2 
3 // ANSI C
4 #include <cstdlib>
5 
6 // Qt
7 #include <QVector>
8 #include <QSqlDriver>
9 #include <QSemaphore>
10 #include <QSqlError>
11 #include <QSqlField>
12 #include <QSqlRecord>
13 
14 // MythTV
15 #include "compat.h"
16 #include "mythdbcon.h"
17 #include "mythdb.h"
18 #include "mythcorecontext.h"
19 #include "mythlogging.h"
20 #include "mythsystem.h"
21 #include "exitcodes.h"
22 #include "mthread.h"
23 #include "mythdate.h"
24 
25 #define DEBUG_RECONNECT 0
26 #if DEBUG_RECONNECT
27 #include <stdlib.h>
28 #endif
29 
30 static const uint kPurgeTimeout = 60 * 60;
31 
32 bool TestDatabase(QString dbHostName,
33  QString dbUserName,
34  QString dbPassword,
35  QString dbName,
36  int dbPort)
37 {
38  bool ret = false;
39 
40  if (dbHostName.isEmpty() || dbUserName.isEmpty())
41  return ret;
42 
43  MSqlDatabase *db = new MSqlDatabase("dbtest");
44 
45  if (!db)
46  return ret;
47 
48  DatabaseParams dbparms;
49  dbparms.dbName = dbName;
50  dbparms.dbUserName = dbUserName;
51  dbparms.dbPassword = dbPassword;
52  dbparms.dbHostName = dbHostName;
53  dbparms.dbPort = dbPort;
54 
55  // Just use some sane defaults for these values
56  dbparms.wolEnabled = false;
57  dbparms.wolReconnect = 1;
58  dbparms.wolRetry = 3;
59  dbparms.wolCommand = QString();
60 
61  db->SetDBParams(dbparms);
62 
63  ret = db->OpenDatabase(true);
64 
65  delete db;
66  db = NULL;
67 
68  return ret;
69 }
70 
72 {
73  m_name = name;
74  m_name.detach();
75  m_db = QSqlDatabase::addDatabase("QMYSQL", m_name);
76  LOG(VB_DATABASE, LOG_INFO, "Database connection created: " + m_name);
77 
78  if (!m_db.isValid())
79  {
80  LOG(VB_GENERAL, LOG_ERR, "Unable to init db connection.");
81  return;
82  }
83  m_lastDBKick = MythDate::current().addSecs(-60);
84 }
85 
87 {
88  if (m_db.isOpen())
89  {
90  m_db.close();
91  m_db = QSqlDatabase(); // forces a destroy and must be done before
92  // removeDatabase() so that connections
93  // and queries are cleaned up correctly
94  QSqlDatabase::removeDatabase(m_name);
95  LOG(VB_DATABASE, LOG_INFO, "Database connection deleted: " + m_name);
96  }
97 }
98 
100 {
101  if (m_db.isValid())
102  {
103  if (m_db.isOpen())
104  return true;
105  }
106  return false;
107 }
108 
109 bool MSqlDatabase::OpenDatabase(bool skipdb)
110 {
111  if (!m_db.isValid())
112  {
113  LOG(VB_GENERAL, LOG_ERR,
114  "MSqlDatabase::OpenDatabase(), db object is not valid!");
115  return false;
116  }
117 
118  bool connected = true;
119 
120  if (!m_db.isOpen())
121  {
122  if (!skipdb)
124  m_db.setDatabaseName(m_dbparms.dbName);
125  m_db.setUserName(m_dbparms.dbUserName);
126  m_db.setPassword(m_dbparms.dbPassword);
127  m_db.setHostName(m_dbparms.dbHostName);
128 
129  if (m_dbparms.dbHostName.isEmpty()) // Bootstrapping without a database?
130  {
131  connected = true; // Pretend to be connected
132  return true; // to reduce errors
133  }
134 
135  if (m_dbparms.dbPort)
136  m_db.setPort(m_dbparms.dbPort);
137 
138  // Prefer using the faster localhost connection if using standard
139  // ports, even if the user specified a DBHostName of 127.0.0.1. This
140  // will cause MySQL to use a Unix socket (on *nix) or shared memory (on
141  // Windows) connection.
142  if ((m_dbparms.dbPort == 0 || m_dbparms.dbPort == 3306) &&
143  m_dbparms.dbHostName == "127.0.0.1")
144  m_db.setHostName("localhost");
145 
146  connected = m_db.open();
147 
148  if (!connected && m_dbparms.wolEnabled)
149  {
150  int trycount = 0;
151 
152  while (!connected && trycount++ < m_dbparms.wolRetry)
153  {
154  LOG(VB_GENERAL, LOG_INFO,
155  QString("Using WOL to wakeup database server (Try %1 of "
156  "%2)")
157  .arg(trycount).arg(m_dbparms.wolRetry));
158 
159  if (myth_system(m_dbparms.wolCommand) != GENERIC_EXIT_OK)
160  {
161  LOG(VB_GENERAL, LOG_ERR,
162  QString("Failed to run WOL command '%1'")
163  .arg(m_dbparms.wolCommand));
164  }
165 
167  connected = m_db.open();
168  }
169 
170  if (!connected)
171  {
172  LOG(VB_GENERAL, LOG_ERR,
173  "WOL failed, unable to connect to database!");
174  }
175  }
176  if (connected)
177  {
178  LOG(VB_DATABASE, LOG_INFO,
179  QString("Connected to database '%1' at host: %2")
180  .arg(m_db.databaseName()).arg(m_db.hostName()));
181 
182  InitSessionVars();
183 
184  // WriteDelayed depends on SetHaveDBConnection() and SetHaveSchema()
185  // both being called with true, so order is important here.
187  if (!GetMythDB()->HaveSchema())
188  {
189  // We can't just check the count of QSqlDatabase::tables()
190  // because it returns all tables visible to the user in *all*
191  // databases (not just the current DB).
192  bool have_schema = false;
193  QString sql = "SELECT COUNT( "
194  " INFORMATION_SCHEMA.TABLES.TABLE_NAME "
195  " ) "
196  " FROM INFORMATION_SCHEMA.TABLES "
197  " WHERE INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA "
198  " = DATABASE() "
199  " AND INFORMATION_SCHEMA.TABLES.TABLE_TYPE = "
200  " 'BASE TABLE';";
201  // We can't use MSqlQuery to determine if we have a schema,
202  // since it will open a new connection, which will try to check
203  // if we have a schema
204  QSqlQuery query = m_db.exec(sql); // don't convert to MSqlQuery
205  if (query.next())
206  have_schema = query.value(0).toInt() > 1;
207  GetMythDB()->SetHaveSchema(have_schema);
208  }
210  }
211  }
212 
213  if (!connected)
214  {
215  GetMythDB()->SetHaveDBConnection(false);
216  LOG(VB_GENERAL, LOG_ERR, "Unable to connect to database!");
217  LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(m_db.lastError()));
218  }
219 
220  return connected;
221 }
222 
224 {
225  m_lastDBKick = MythDate::current().addSecs(-60);
226 
227  if (!m_db.isOpen())
228  m_db.open();
229 
230  return m_db.isOpen();
231 }
232 
234 {
235  m_db.close();
236  m_db.open();
237 
238  bool open = m_db.isOpen();
239  if (open)
240  {
241  LOG(VB_GENERAL, LOG_INFO, "MySQL reconnected successfully");
242  InitSessionVars();
243  }
244 
245  return open;
246 }
247 
249 {
250  // Make sure NOW() returns time in UTC...
251  m_db.exec("SET @@session.time_zone='+00:00'");
252  // Disable strict mode
253  m_db.exec("SET @@session.sql_mode=''");
254 }
255 
256 // -----------------------------------------------------------------------
257 
258 
259 
261 {
262  m_nextConnID = 0;
263  m_connCount = 0;
264 
265  m_schedCon = NULL;
266  m_DDCon = NULL;
267 }
268 
270 {
271  CloseDatabases();
272 
273  if (m_connCount != 0 || m_schedCon || m_DDCon)
274  {
275  LOG(VB_GENERAL, LOG_CRIT,
276  "MDBManager exiting with connections still open");
277  }
278 #if 0 /* some post logStop() debugging... */
279  cout<<"m_connCount: "<<m_connCount<<endl;
280  cout<<"m_schedCon: "<<m_schedCon<<endl;
281  cout<<"m_DDCon: "<<m_DDCon<<endl;
282 #endif
283 }
284 
286 {
287  PurgeIdleConnections(true);
288 
289  m_lock.lock();
290 
291  MSqlDatabase *db;
292 
293 #if REUSE_CONNECTION
294  if (reuse)
295  {
296  db = m_inuse[QThread::currentThread()];
297  if (db != NULL)
298  {
299  m_inuse_count[QThread::currentThread()]++;
300  m_lock.unlock();
301  return db;
302  }
303  }
304 #endif
305 
306  DBList &list = m_pool[QThread::currentThread()];
307  if (list.isEmpty())
308  {
309  db = new MSqlDatabase("DBManager" + QString::number(m_nextConnID++));
310  ++m_connCount;
311  LOG(VB_DATABASE, LOG_INFO,
312  QString("New DB connection, total: %1").arg(m_connCount));
313  }
314  else
315  {
316  db = list.back();
317  list.pop_back();
318  }
319 
320 #if REUSE_CONNECTION
321  if (reuse)
322  {
323  m_inuse_count[QThread::currentThread()]=1;
324  m_inuse[QThread::currentThread()] = db;
325  }
326 #endif
327 
328  m_lock.unlock();
329 
330  db->OpenDatabase();
331 
332  return db;
333 }
334 
336 {
337  m_lock.lock();
338 
339 #if REUSE_CONNECTION
340  if (db == m_inuse[QThread::currentThread()])
341  {
342  int cnt = --m_inuse_count[QThread::currentThread()];
343  if (cnt > 0)
344  {
345  m_lock.unlock();
346  return;
347  }
348  m_inuse[QThread::currentThread()] = NULL;
349  }
350 #endif
351 
352  if (db)
353  {
355  m_pool[QThread::currentThread()].push_front(db);
356  }
357 
358  m_lock.unlock();
359 
360  PurgeIdleConnections(true);
361 }
362 
364 {
365  QMutexLocker locker(&m_lock);
366 
367  leaveOne = leaveOne || (gCoreContext && gCoreContext->IsUIThread());
368 
369  QDateTime now = MythDate::current();
370  DBList &list = m_pool[QThread::currentThread()];
371  DBList::iterator it = list.begin();
372 
373  uint purgedConnections = 0, totalConnections = 0;
374  MSqlDatabase *newDb = NULL;
375  while (it != list.end())
376  {
377  totalConnections++;
378  if ((*it)->m_lastDBKick.secsTo(now) <= (int)kPurgeTimeout)
379  {
380  ++it;
381  continue;
382  }
383 
384  // This connection has not been used in the kPurgeTimeout
385  // seconds close it.
386  MSqlDatabase *entry = *it;
387  it = list.erase(it);
388  --m_connCount;
389  purgedConnections++;
390 
391  // Qt's MySQL driver apparently keeps track of the number of
392  // open DB connections, and when it hits 0, calls
393  // my_thread_global_end(). The mysql library then assumes the
394  // application is ending and that all threads that created DB
395  // connections have already exited. This is rarely true, and
396  // may result in the mysql library pausing 5 seconds and
397  // printing a message like "Error in my_thread_global_end(): 1
398  // threads didn't exit". This workaround simply creates an
399  // extra DB connection before all pooled connections are
400  // purged so that my_thread_global_end() won't be called.
401  if (leaveOne && it == list.end() &&
402  purgedConnections > 0 &&
403  totalConnections == purgedConnections)
404  {
405  newDb = new MSqlDatabase("DBManager" +
406  QString::number(m_nextConnID++));
407  ++m_connCount;
408  LOG(VB_GENERAL, LOG_INFO,
409  QString("New DB connection, total: %1").arg(m_connCount));
410  newDb->m_lastDBKick = MythDate::current();
411  }
412 
413  LOG(VB_DATABASE, LOG_INFO, "Deleting idle DB connection...");
414  delete entry;
415  LOG(VB_DATABASE, LOG_INFO, "Done deleting idle DB connection.");
416  }
417  if (newDb)
418  list.push_front(newDb);
419 
420  if (purgedConnections)
421  {
422  LOG(VB_DATABASE, LOG_INFO,
423  QString("Purged %1 idle of %2 total DB connections.")
424  .arg(purgedConnections).arg(totalConnections));
425  }
426 }
427 
429 {
430  if (!dbcon)
431  return NULL;
432 
433  if (!*dbcon)
434  {
435  *dbcon = new MSqlDatabase(name);
436  LOG(VB_GENERAL, LOG_INFO, "New static DB connection" + name);
437  }
438 
439  (*dbcon)->OpenDatabase();
440 
441  if (!m_static_pool[QThread::currentThread()].contains(*dbcon))
442  m_static_pool[QThread::currentThread()].push_back(*dbcon);
443 
444  return *dbcon;
445 }
446 
448 {
449  return getStaticCon(&m_schedCon, "SchedCon");
450 }
451 
453 {
454  return getStaticCon(&m_DDCon, "DataDirectCon");
455 }
456 
458 {
459  m_lock.lock();
460  DBList list = m_pool[QThread::currentThread()];
461  m_pool[QThread::currentThread()].clear();
462  m_lock.unlock();
463 
464  for (DBList::iterator it = list.begin(); it != list.end(); ++it)
465  {
466  LOG(VB_DATABASE, LOG_INFO,
467  "Closing DB connection named '" + (*it)->m_name + "'");
468  (*it)->m_db.close();
469  delete (*it);
470  m_connCount--;
471  }
472 
473  m_lock.lock();
474  DBList &slist = m_static_pool[QThread::currentThread()];
475  while (!slist.isEmpty())
476  {
477  MSqlDatabase *db = slist.takeFirst();
478  LOG(VB_DATABASE, LOG_INFO,
479  "Closing DB connection named '" + db->m_name + "'");
480  db->m_db.close();
481  delete db;
482 
483  if (db == m_schedCon)
484  m_schedCon = NULL;
485  if (db == m_DDCon)
486  m_DDCon = NULL;
487  }
488  m_lock.unlock();
489 }
490 
491 
492 // -----------------------------------------------------------------------
493 
495 {
496  qi.db = NULL;
497  qi.qsqldb = QSqlDatabase();
498  qi.returnConnection = true;
499 }
500 
501 
503  : QSqlQuery(QString::null, qi.qsqldb)
504 {
505  m_isConnected = false;
506  m_db = qi.db;
508 
509  m_isConnected = m_db && m_db->isOpen();
510 
511 #ifdef DEBUG_QT4_PORT
512  m_testbindings = QRegExp("(:\\w+)\\W.*\\1\\b");
513 #endif
514 }
515 
517 {
518  if (m_returnConnection)
519  {
520  MDBManager *dbmanager = GetMythDB()->GetDBManager();
521 
522  if (dbmanager && m_db)
523  {
524  dbmanager->pushConnection(m_db);
525  }
526  }
527 }
528 
530 {
531  bool reuse = kNormalConnection == _reuse;
533  MSqlQueryInfo qi;
534 
535  InitMSqlQueryInfo(qi);
536 
537 
538  // Bootstrapping without a database?
539  //if (db->pretendHaveDB)
540  if (db->m_db.hostName().isEmpty())
541  {
542  // Return an invalid database so that QSqlQuery does nothing.
543  // Also works around a Qt4 bug where QSqlQuery::~QSqlQuery
544  // calls QMYSQLResult::cleanup() which uses mysql_next_result()
545 
547  qi.returnConnection = false;
548  return qi;
549  }
550 
551  qi.db = db;
552  qi.qsqldb = db->db();
553 
554  db->KickDatabase();
555 
556  return qi;
557 }
558 
560 {
562  MSqlQueryInfo qi;
563 
564  InitMSqlQueryInfo(qi);
565  qi.returnConnection = false;
566 
567  if (db)
568  {
569  qi.db = db;
570  qi.qsqldb = db->db();
571 
572  db->KickDatabase();
573  }
574 
575  return qi;
576 }
577 
579 {
581  MSqlQueryInfo qi;
582 
583  InitMSqlQueryInfo(qi);
584  qi.returnConnection = false;
585 
586  if (db)
587  {
588  qi.db = db;
589  qi.qsqldb = db->db();
590 
591  db->KickDatabase();
592  }
593 
594  return qi;
595 }
596 
598 {
599  if (!m_db)
600  {
601  // Database structure's been deleted
602  return false;
603  }
604 
605  if (m_last_prepared_query.isEmpty())
606  {
607  LOG(VB_GENERAL, LOG_ERR,
608  "MSqlQuery::exec(void) called without a prepared query.");
609  return false;
610  }
611 
612 #if DEBUG_RECONNECT
613  if (random() < RAND_MAX / 50)
614  {
615  LOG(VB_GENERAL, LOG_INFO,
616  "MSqlQuery disconnecting DB to test reconnection logic");
617  m_db->m_db.close();
618  }
619 #endif
620 
621  // Database connection down. Try to restart it, give up if it's still
622  // down
623  if (!m_db->isOpen() && !Reconnect())
624  {
625  LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected");
626  return false;
627  }
628 
629  bool result = QSqlQuery::exec();
630 
631  // if the query failed with "MySQL server has gone away"
632  // Close and reopen the database connection and retry the query if it
633  // connects again
634  if (!result && QSqlQuery::lastError().number() == 2006 && Reconnect())
635  result = QSqlQuery::exec();
636 
637  if (!result)
638  {
639  QString err = MythDB::GetError("MSqlQuery", *this);
641  bool has_null_strings = false;
642  for (MSqlBindings::iterator it = tmp.begin(); it != tmp.end(); ++it)
643  {
644  if (it->type() != QVariant::String)
645  continue;
646  if (it->isNull() || it->toString().isNull())
647  {
648  has_null_strings = true;
649  *it = QVariant(QString(""));
650  }
651  }
652  if (has_null_strings)
653  {
654  bindValues(tmp);
655  result = QSqlQuery::exec();
656  }
657  if (result)
658  {
659  LOG(VB_GENERAL, LOG_ERR,
660  QString("Original query failed, but resend with empty "
661  "strings in place of NULL strings worked. ") +
662  "\n" + err);
663  }
664  }
665 
666  if (VERBOSE_LEVEL_CHECK(VB_DATABASE, LOG_DEBUG))
667  {
668  QString str = lastQuery();
669 
670  // Database logging will cause an infinite loop here if not filtered
671  // out
672  if (!str.startsWith("INSERT INTO logging "))
673  {
674  // Sadly, neither executedQuery() nor lastQuery() display
675  // the values in bound queries against a MySQL5 database.
676  // So, replace the named placeholders with their values.
677 
678  QMapIterator<QString, QVariant> b = boundValues();
679  while (b.hasNext())
680  {
681  b.next();
682  str.replace(b.key(), '\'' + b.value().toString() + '\'');
683  }
684 
685  LOG(VB_DATABASE, LOG_DEBUG,
686  QString("MSqlQuery::exec(%1) %2%3")
687  .arg(m_db->MSqlDatabase::GetConnectionName()).arg(str)
688  .arg(isSelect() ? QString(" <<<< Returns %1 row(s)")
689  .arg(size()) : QString()));
690  }
691  }
692 
693  return result;
694 }
695 
696 bool MSqlQuery::exec(const QString &query)
697 {
698  if (!m_db)
699  {
700  // Database structure's been deleted
701  return false;
702  }
703 
704  // Database connection down. Try to restart it, give up if it's still
705  // down
706  if (!m_db->isOpen() && !Reconnect())
707  {
708  LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected");
709  return false;
710  }
711 
712  bool result = QSqlQuery::exec(query);
713 
714  // if the query failed with "MySQL server has gone away"
715  // Close and reopen the database connection and retry the query if it
716  // connects again
717  if (!result && QSqlQuery::lastError().number() == 2006 && Reconnect())
718  result = QSqlQuery::exec(query);
719 
720  LOG(VB_DATABASE, LOG_DEBUG,
721  QString("MSqlQuery::exec(%1) %2%3")
722  .arg(m_db->MSqlDatabase::GetConnectionName()).arg(query)
723  .arg(isSelect() ? QString(" <<<< Returns %1 row(s)")
724  .arg(size()) : QString()));
725 
726  return result;
727 }
728 
729 bool MSqlQuery::seekDebug(const char *type, bool result,
730  int where, bool relative) const
731 {
732  if (result && VERBOSE_LEVEL_CHECK(VB_DATABASE, LOG_DEBUG))
733  {
734  QString str;
735  QSqlRecord rec = record();
736 
737  for (long int i = 0; i < rec.count(); i++)
738  {
739  if (!str.isEmpty())
740  str.append(", ");
741 
742  str.append(rec.fieldName(i) + " = " +
743  value(i).toString());
744  }
745 
746  if (QString("seek")==type)
747  {
748  LOG(VB_DATABASE, LOG_DEBUG,
749  QString("MSqlQuery::seek(%1,%2,%3) Result: \"%4\"")
750  .arg(m_db->MSqlDatabase::GetConnectionName())
751  .arg(where).arg(relative)
752  .arg(str));
753  }
754  else
755  {
756  LOG(VB_DATABASE, LOG_DEBUG,
757  QString("MSqlQuery::%1(%2) Result: \"%3\"")
758  .arg(type).arg(m_db->MSqlDatabase::GetConnectionName())
759  .arg(str));
760  }
761  }
762  return result;
763 }
764 
765 bool MSqlQuery::next(void)
766 {
767  return seekDebug("next", QSqlQuery::next(), 0, false);
768 }
769 
771 {
772  return seekDebug("previous", QSqlQuery::previous(), 0, false);
773 }
774 
776 {
777  return seekDebug("first", QSqlQuery::first(), 0, false);
778 }
779 
780 bool MSqlQuery::last(void)
781 {
782  return seekDebug("last", QSqlQuery::last(), 0, false);
783 }
784 
785 bool MSqlQuery::seek(int where, bool relative)
786 {
787  return seekDebug("seek", QSqlQuery::seek(where, relative), where, relative);
788 }
789 
790 bool MSqlQuery::prepare(const QString& query)
791 {
792  if (!m_db)
793  {
794  // Database structure's been deleted
795  return false;
796  }
797 
798  m_last_prepared_query = query;
799 
800 #ifdef DEBUG_QT4_PORT
801  if (query.contains(m_testbindings))
802  {
803  LOG(VB_GENERAL, LOG_DEBUG,
804  QString("\n\nQuery contains bind value \"%1\" twice:\n\n\n")
805  .arg(m_testbindings.cap(1)) + query);
806 #if 0
807  exit(1);
808 #endif
809  }
810 #endif
811 
812  // Database connection down. Try to restart it, give up if it's still
813  // down
814  if (!m_db)
815  {
816  // Database structure has been deleted...
817  return false;
818  }
819 
820  if (!m_db->isOpen() && !Reconnect())
821  {
822  LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected");
823  return false;
824  }
825 
826  bool ok = QSqlQuery::prepare(query);
827 
828  // if the prepare failed with "MySQL server has gone away"
829  // Close and reopen the database connection and retry the query if it
830  // connects again
831  if (!ok && QSqlQuery::lastError().number() == 2006 && Reconnect())
832  ok = true;
833 
834  if (!ok && !(GetMythDB()->SuppressDBMessages()))
835  {
836  LOG(VB_GENERAL, LOG_ERR,
837  QString("Error preparing query: %1").arg(query));
838  LOG(VB_GENERAL, LOG_ERR,
839  MythDB::DBErrorMessage(QSqlQuery::lastError()));
840  }
841 
842  return ok;
843 }
844 
846 {
848 
849  // popConnection() has already called OpenDatabase(),
850  // so we only have to check if it was successful:
851  bool isOpen = db->isOpen();
852 
854  return isOpen;
855 }
856 
857 void MSqlQuery::bindValue(const QString &placeholder, const QVariant &val)
858 {
859 #ifdef DEBUG_QT4_PORT
860  // XXX - HACK BEGIN
861  // qt4 doesn't like to bind values without occurrence in the prepared query
862  if (!m_last_prepared_query.contains(placeholder))
863  {
864  LOG(VB_GENERAL, LOG_ERR, "Trying to bind a value to placeholder " +
865  placeholder + " without occurrence in the prepared query."
866  " Ignoring it.\nQuery was: \"" + m_last_prepared_query + "\"");
867  return;
868  }
869  // XXX - HACK END
870 #endif
871 
872  QSqlQuery::bindValue(placeholder, val, QSql::In);
873 }
874 
875 void MSqlQuery::bindValues(const MSqlBindings &bindings)
876 {
877  MSqlBindings::const_iterator it;
878  for (it = bindings.begin(); it != bindings.end(); ++it)
879  {
880  bindValue(it.key(), it.value());
881  }
882 }
883 
885 {
886  return QSqlQuery::lastInsertId();
887 }
888 
890 {
891  if (!m_db->Reconnect())
892  return false;
893  if (!m_last_prepared_query.isEmpty())
894  {
896  if (!QSqlQuery::prepare(m_last_prepared_query))
897  return false;
898  bindValues(tmp);
899  }
900  return true;
901 }
902 
904 {
905  MSqlBindings::Iterator it;
906  for (it = addfrom.begin(); it != addfrom.end(); ++it)
907  {
908  output.insert(it.key(), it.value());
909  }
910 }
911 
912 struct Holder {
913  Holder( const QString& hldr = QString::null, int pos = -1 )
914  : holderName( hldr ), holderPos( pos ) {}
915 
916  bool operator==( const Holder& h ) const
917  { return h.holderPos == holderPos && h.holderName == holderName; }
918  bool operator!=( const Holder& h ) const
919  { return h.holderPos != holderPos || h.holderName != holderName; }
920  QString holderName;
922 };
923 
924 void MSqlEscapeAsAQuery(QString &query, MSqlBindings &bindings)
925 {
926  MSqlQuery result(MSqlQuery::InitCon());
927 
928  QString q = query;
929  QRegExp rx(QString::fromLatin1("'[^']*'|:([a-zA-Z0-9_]+)"));
930 
931  QVector<Holder> holders;
932 
933  int i = 0;
934  while ((i = rx.indexIn(q, i)) != -1)
935  {
936  if (!rx.cap(1).isEmpty())
937  holders.append(Holder(rx.cap(0), i));
938  i += rx.matchedLength();
939  }
940 
941  QVariant val;
942  QString holder;
943 
944  for (i = (int)holders.count() - 1; i >= 0; --i)
945  {
946  holder = holders[(uint)i].holderName;
947  val = bindings[holder];
948  QSqlField f("", val.type());
949  if (val.isNull())
950  f.clear();
951  else
952  f.setValue(val);
953 
954  query = query.replace((uint)holders[(uint)i].holderPos, holder.length(),
955  result.driver()->formatValue(f));
956  }
957 }
958