+
+bool PostgreSqlStorage::beginTransaction(QSqlDatabase &db)
+{
+ bool result = db.transaction();
+ if (!db.isOpen()) {
+ db = logDb();
+ result = db.transaction();
+ }
+ return result;
+}
+
+bool PostgreSqlStorage::beginReadOnlyTransaction(QSqlDatabase &db)
+{
+ QSqlQuery query = db.exec("BEGIN TRANSACTION READ ONLY");
+ if (!db.isOpen()) {
+ db = logDb();
+ query = db.exec("BEGIN TRANSACTION READ ONLY");
+ }
+ return !query.lastError().isValid();
+}
+
+
+QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, const QString ¶mstring, QSqlDatabase &db)
+{
+ // Query preparing is done lazily. That means that instead of always checking if the query is already prepared
+ // we just EXECUTE and catch the error
+ QSqlQuery query;
+
+ db.exec("SAVEPOINT quassel_prepare_query");
+ if (paramstring.isNull()) {
+ query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
+ }
+ else {
+ query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
+ }
+
+ if (!db.isOpen() || db.lastError().isValid()) {
+ // If the query failed because the DB connection was down, reopen the connection and start a new transaction.
+ if (!db.isOpen()) {
+ db = logDb();
+ if (!beginTransaction(db)) {
+ qWarning() << "PostgreSqlStorage::prepareAndExecuteQuery(): cannot start transaction while recovering from connection loss!";
+ qWarning() << " -" << qPrintable(db.lastError().text());
+ return query;
+ }
+ db.exec("SAVEPOINT quassel_prepare_query");
+ } else {
+ db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query");
+ }
+
+ // and once again: Qt leaves us without error codes so we either parse (language dependent(!)) strings
+ // or we just guess the error. As we're only interested in unprepared queries, this will be our guess. :)
+ QSqlQuery checkQuery = db.exec(QString("SELECT count(name) FROM pg_prepared_statements WHERE name = 'quassel_%1' AND from_sql = TRUE").arg(queryname.toLower()));
+ checkQuery.first();
+ if (checkQuery.value(0).toInt() == 0) {
+ db.exec(QString("PREPARE quassel_%1 AS %2").arg(queryname).arg(queryString(queryname)));
+ if (db.lastError().isValid()) {
+ qWarning() << "PostgreSqlStorage::prepareQuery(): unable to prepare query:" << queryname << "AS" << queryString(queryname);
+ qWarning() << " Error:" << db.lastError().text();
+ return QSqlQuery(db);
+ }
+ }
+ // we always execute the query again, even if the query was already prepared.
+ // this ensures, that the error is properly propagated to the calling function
+ // (otherwise the last call would be the testing select to pg_prepared_statements
+ // which always gives a proper result and the error would be lost)
+ if (paramstring.isNull()) {
+ query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
+ }
+ else {
+ query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
+ }
+ }
+ else {
+ // only release the SAVEPOINT
+ db.exec("RELEASE SAVEPOINT quassel_prepare_query");
+ }
+ return query;
+}
+
+
+QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList ¶ms, QSqlDatabase &db)
+{
+ QSqlDriver *driver = db.driver();
+
+ QStringList paramStrings;
+ QSqlField field;
+ for (int i = 0; i < params.count(); i++) {
+ const QVariant &value = params.at(i);
+ field.setType(value.type());
+ if (value.isNull())
+ field.clear();
+ else
+ field.setValue(value);
+
+ paramStrings << driver->formatValue(field);
+ }
+
+ if (params.isEmpty()) {
+ return prepareAndExecuteQuery(queryname, db);
+ }
+ else {
+ return prepareAndExecuteQuery(queryname, paramStrings.join(", "), db);
+ }
+}
+
+
+QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant ¶m, QSqlDatabase &db)
+{
+ QSqlField field;
+ field.setType(param.type());
+ if (param.isNull())
+ field.clear();
+ else
+ field.setValue(param);
+
+ QString paramString = db.driver()->formatValue(field);
+ return prepareAndExecuteQuery(queryname, paramString, db);
+}
+
+
+void PostgreSqlStorage::deallocateQuery(const QString &queryname, const QSqlDatabase &db)
+{
+ db.exec(QString("DEALLOCATE quassel_%1").arg(queryname));
+}
+
+
+void PostgreSqlStorage::safeExec(QSqlQuery &query)
+{
+ // If the query fails due to the connection being gone, it seems to cause
+ // exec() to return false but no lastError to be set
+ if(!query.exec() && !query.lastError().isValid())
+ {
+ QSqlDatabase db = logDb();
+ QSqlQuery retryQuery(db);
+ retryQuery.prepare(query.lastQuery());
+ QMapIterator<QString, QVariant> i(query.boundValues());
+ while (i.hasNext())
+ {
+ i.next();
+ retryQuery.bindValue(i.key(),i.value());
+ }
+ query = retryQuery;
+ query.exec();
+ }
+}
+
+// ========================================
+// PostgreSqlMigrationWriter
+// ========================================
+PostgreSqlMigrationWriter::PostgreSqlMigrationWriter()
+ : PostgreSqlStorage()
+{
+}
+
+
+bool PostgreSqlMigrationWriter::prepareQuery(MigrationObject mo)
+{
+ QString query;
+ switch (mo) {
+ case QuasselUser:
+ query = queryString("migrate_write_quasseluser");
+ break;
+ case Sender:
+ query = queryString("migrate_write_sender");
+ break;
+ case Identity:
+ _validIdentities.clear();
+ query = queryString("migrate_write_identity");
+ break;
+ case IdentityNick:
+ query = queryString("migrate_write_identity_nick");
+ break;
+ case Network:
+ query = queryString("migrate_write_network");
+ break;
+ case Buffer:
+ query = queryString("migrate_write_buffer");
+ break;
+ case Backlog:
+ query = queryString("migrate_write_backlog");
+ break;
+ case IrcServer:
+ query = queryString("migrate_write_ircserver");
+ break;
+ case UserSetting:
+ query = queryString("migrate_write_usersetting");
+ break;
+ }
+ newQuery(query, logDb());
+ return true;
+}
+
+
+//bool PostgreSqlMigrationWriter::writeUser(const QuasselUserMO &user) {
+bool PostgreSqlMigrationWriter::writeMo(const QuasselUserMO &user)
+{
+ bindValue(0, user.id.toInt());
+ bindValue(1, user.username);
+ bindValue(2, user.password);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeSender(const SenderMO &sender) {
+bool PostgreSqlMigrationWriter::writeMo(const SenderMO &sender)
+{
+ bindValue(0, sender.senderId);
+ bindValue(1, sender.sender);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeIdentity(const IdentityMO &identity) {
+bool PostgreSqlMigrationWriter::writeMo(const IdentityMO &identity)
+{
+ _validIdentities << identity.id.toInt();
+ bindValue(0, identity.id.toInt());
+ bindValue(1, identity.userid.toInt());
+ bindValue(2, identity.identityname);
+ bindValue(3, identity.realname);
+ bindValue(4, identity.awayNick);
+ bindValue(5, identity.awayNickEnabled);
+ bindValue(6, identity.awayReason);
+ bindValue(7, identity.awayReasonEnabled);
+ bindValue(8, identity.autoAwayEnabled);
+ bindValue(9, identity.autoAwayTime);
+ bindValue(10, identity.autoAwayReason);
+ bindValue(11, identity.autoAwayReasonEnabled);
+ bindValue(12, identity.detachAwayEnabled);
+ bindValue(13, identity.detachAwayReason);
+ bindValue(14, identity.detchAwayReasonEnabled);
+ bindValue(15, identity.ident);
+ bindValue(16, identity.kickReason);
+ bindValue(17, identity.partReason);
+ bindValue(18, identity.quitReason);
+ bindValue(19, identity.sslCert);
+ bindValue(20, identity.sslKey);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeIdentityNick(const IdentityNickMO &identityNick) {
+bool PostgreSqlMigrationWriter::writeMo(const IdentityNickMO &identityNick)
+{
+ bindValue(0, identityNick.nickid);
+ bindValue(1, identityNick.identityId.toInt());
+ bindValue(2, identityNick.nick);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeNetwork(const NetworkMO &network) {
+bool PostgreSqlMigrationWriter::writeMo(const NetworkMO &network)
+{
+ bindValue(0, network.networkid.toInt());
+ bindValue(1, network.userid.toInt());
+ bindValue(2, network.networkname);
+ if (_validIdentities.contains(network.identityid.toInt()))
+ bindValue(3, network.identityid.toInt());
+ else
+ bindValue(3, QVariant());
+ bindValue(4, network.encodingcodec);
+ bindValue(5, network.decodingcodec);
+ bindValue(6, network.servercodec);
+ bindValue(7, network.userandomserver);
+ bindValue(8, network.perform);
+ bindValue(9, network.useautoidentify);
+ bindValue(10, network.autoidentifyservice);
+ bindValue(11, network.autoidentifypassword);
+ bindValue(12, network.useautoreconnect);
+ bindValue(13, network.autoreconnectinterval);
+ bindValue(14, network.autoreconnectretries);
+ bindValue(15, network.unlimitedconnectretries);
+ bindValue(16, network.rejoinchannels);
+ bindValue(17, network.connected);
+ bindValue(18, network.usermode);
+ bindValue(19, network.awaymessage);
+ bindValue(20, network.attachperform);
+ bindValue(21, network.detachperform);
+ bindValue(22, network.usesasl);
+ bindValue(23, network.saslaccount);
+ bindValue(24, network.saslpassword);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeBuffer(const BufferMO &buffer) {
+bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
+{
+ bindValue(0, buffer.bufferid.toInt());
+ bindValue(1, buffer.userid.toInt());
+ bindValue(2, buffer.groupid);
+ bindValue(3, buffer.networkid.toInt());
+ bindValue(4, buffer.buffername);
+ bindValue(5, buffer.buffercname);
+ bindValue(6, (int)buffer.buffertype);
+ bindValue(7, buffer.lastseenmsgid);
+ bindValue(8, buffer.markerlinemsgid);
+ bindValue(9, buffer.key);
+ bindValue(10, buffer.joined);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeBacklog(const BacklogMO &backlog) {
+bool PostgreSqlMigrationWriter::writeMo(const BacklogMO &backlog)
+{
+ bindValue(0, backlog.messageid.toInt());
+ bindValue(1, backlog.time);
+ bindValue(2, backlog.bufferid.toInt());
+ bindValue(3, backlog.type);
+ bindValue(4, (int)backlog.flags);
+ bindValue(5, backlog.senderid);
+ bindValue(6, backlog.message);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeIrcServer(const IrcServerMO &ircserver) {
+bool PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver)
+{
+ bindValue(0, ircserver.serverid);
+ bindValue(1, ircserver.userid.toInt());
+ bindValue(2, ircserver.networkid.toInt());
+ bindValue(3, ircserver.hostname);
+ bindValue(4, ircserver.port);
+ bindValue(5, ircserver.password);
+ bindValue(6, ircserver.ssl);
+ bindValue(7, ircserver.sslversion);
+ bindValue(8, ircserver.useproxy);
+ bindValue(9, ircserver.proxytype);
+ bindValue(10, ircserver.proxyhost);
+ bindValue(11, ircserver.proxyport);
+ bindValue(12, ircserver.proxyuser);
+ bindValue(13, ircserver.proxypass);
+ return exec();
+}
+
+
+//bool PostgreSqlMigrationWriter::writeUserSetting(const UserSettingMO &userSetting) {
+bool PostgreSqlMigrationWriter::writeMo(const UserSettingMO &userSetting)
+{
+ bindValue(0, userSetting.userid.toInt());
+ bindValue(1, userSetting.settingname);
+ bindValue(2, userSetting.settingvalue);
+ return exec();
+}
+
+
+bool PostgreSqlMigrationWriter::postProcess()
+{
+ QSqlDatabase db = logDb();
+ QList<Sequence> sequences;
+ sequences << Sequence("backlog", "messageid")
+ << Sequence("buffer", "bufferid")
+ << Sequence("identity", "identityid")
+ << Sequence("identity_nick", "nickid")
+ << Sequence("ircserver", "serverid")
+ << Sequence("network", "networkid")
+ << Sequence("quasseluser", "userid")
+ << Sequence("sender", "senderid");
+ QList<Sequence>::const_iterator iter;
+ for (iter = sequences.constBegin(); iter != sequences.constEnd(); ++iter) {
+ resetQuery();
+ newQuery(QString("SELECT setval('%1_%2_seq', max(%2)) FROM %1").arg(iter->table, iter->field), db);
+ if (!exec())
+ return false;
+ }
+ return true;