X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcore%2Fpostgresqlstorage.cpp;h=237876917c7fc40823de7903361e0184ca3ac6ed;hp=3965704608ada44b5c5ef08847d7dfbdffb98b81;hb=4dfe52421b1b6a71b3c0bda2452edf3605f59528;hpb=4c80eeb2d07b5ca75fd399b51c939961fdff1670 diff --git a/src/core/postgresqlstorage.cpp b/src/core/postgresqlstorage.cpp index 39657046..23787691 100644 --- a/src/core/postgresqlstorage.cpp +++ b/src/core/postgresqlstorage.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2013 by the Quassel Project * + * Copyright (C) 2005-2016 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -38,9 +38,9 @@ PostgreSqlStorage::~PostgreSqlStorage() } -AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter() +std::unique_ptr PostgreSqlStorage::createMigrationWriter() { - PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter(); + auto writer = new PostgreSqlMigrationWriter(); QVariantMap properties; properties["Username"] = _userName; properties["Password"] = _password; @@ -48,24 +48,33 @@ AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter() properties["Port"] = _port; properties["Database"] = _databaseName; writer->setConnectionProperties(properties); - return writer; + return std::unique_ptr{writer}; } bool PostgreSqlStorage::isAvailable() const { - qDebug() << QSqlDatabase::drivers(); - if (!QSqlDatabase::isDriverAvailable("QPSQL")) return false; + if (!QSqlDatabase::isDriverAvailable("QPSQL")) { + quWarning() << qPrintable(tr("PostgreSQL driver plugin not available for Qt. Installed drivers:")) + << qPrintable(QSqlDatabase::drivers().join(", ")); + return false; + } return true; } -QString PostgreSqlStorage::displayName() const +QString PostgreSqlStorage::backendId() const { return QString("PostgreSQL"); } +QString PostgreSqlStorage::displayName() const +{ + return backendId(); // Note: Pre-0.13 clients use the displayName property for backend idenfication +} + + QString PostgreSqlStorage::description() const { // FIXME: proper description @@ -73,34 +82,68 @@ QString PostgreSqlStorage::description() const } -QStringList PostgreSqlStorage::setupKeys() const +QVariantList PostgreSqlStorage::setupData() const { - QStringList keys; - keys << "Username" - << "Password" - << "Hostname" - << "Port" - << "Database"; - return keys; + QVariantList data; + data << "Username" << tr("Username") << QString("quassel") + << "Password" << tr("Password") << QString() + << "Hostname" << tr("Hostname") << QString("localhost") + << "Port" << tr("Port") << 5432 + << "Database" << tr("Database") << QString("quassel") + ; + return data; } -QVariantMap PostgreSqlStorage::setupDefaults() const +bool PostgreSqlStorage::initDbSession(QSqlDatabase &db) { - QVariantMap map; - map["Username"] = QVariant(QString("quassel")); - map["Hostname"] = QVariant(QString("localhost")); - map["Port"] = QVariant(5432); - map["Database"] = QVariant(QString("quassel")); - return map; -} + // check whether the Qt driver performs string escaping or not. + // i.e. test if it doubles slashes. + QSqlField testField; + testField.setType(QVariant::String); + testField.setValue("\\"); + QString formattedString = db.driver()->formatValue(testField); + switch(formattedString.count('\\')) { + case 2: + // yes it does... and we cannot do anything to change the behavior of Qt. + // If this is a legacy DB (Postgres < 8.2), then everything is already ok, + // as this is the expected behavior. + // If it is a newer version, switch to legacy mode. + quWarning() << "Switching Postgres to legacy mode. (set standard conforming strings to off)"; + // If the following calls fail, it is a legacy DB anyways, so it doesn't matter + // and no need to check the outcome. + db.exec("set standard_conforming_strings = off"); + db.exec("set escape_string_warning = off"); + break; + case 1: + // ok, so Qt does not escape... + // That means we have to ensure that postgres uses standard conforming strings... + { + QSqlQuery query = db.exec("set standard_conforming_strings = on"); + if (query.lastError().isValid()) { + // We cannot enable standard conforming strings... + // since Quassel does no escaping by itself, this would yield a major vulnerability. + quError() << "Failed to enable standard_conforming_strings for the Postgres db!"; + return false; + } + } + break; + default: + // The slash got replaced with 0 or more than 2 slashes! o_O + quError() << "Your version of Qt does something _VERY_ strange to slashes in QSqlQueries! You should consult your trusted doctor!"; + return false; + break; + } -void PostgreSqlStorage::initDbSession(QSqlDatabase &db) -{ - // this blows... but unfortunately Qt's PG driver forces us to this... - db.exec("set standard_conforming_strings = off"); - db.exec("set escape_string_warning = off"); + // Set the PostgreSQL session timezone to UTC, since we want timestamps stored in UTC + QSqlQuery tzQuery = db.exec("SET timezone = 'UTC'"); + if (tzQuery.lastError().isValid()) { + quError() << "Failed to set timezone to UTC!"; + return false; + } + + return true; } @@ -116,12 +159,17 @@ void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties) int PostgreSqlStorage::installedSchemaVersion() { - QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'"); + QSqlQuery query(logDb()); + query.prepare("SELECT value FROM coreinfo WHERE key = 'schemaversion'"); + safeExec(query); + watchQuery(query); if (query.first()) return query.value(0).toInt(); // maybe it's really old... (schema version 0) - query = logDb().exec("SELECT MAX(version) FROM coreinfo"); + query.prepare("SELECT MAX(version) FROM coreinfo"); + safeExec(query); + watchQuery(query); if (query.first()) return query.value(0).toInt(); @@ -134,10 +182,10 @@ bool PostgreSqlStorage::updateSchemaVersion(int newVersion) QSqlQuery query(logDb()); query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'"); query.bindValue(":version", newVersion); - query.exec(); + safeExec(query); bool success = true; - if (query.lastError().isValid()) { + if (!watchQuery(query)) { qCritical() << "PostgreSqlStorage::updateSchemaVersion(int): Updating schema version failed!"; success = false; } @@ -150,10 +198,10 @@ bool PostgreSqlStorage::setupSchemaVersion(int version) QSqlQuery query(logDb()); query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaversion', :version)"); query.bindValue(":version", version); - query.exec(); + safeExec(query); bool success = true; - if (query.lastError().isValid()) { + if (!watchQuery(query)) { qCritical() << "PostgreSqlStorage::setupSchemaVersion(int): Updating schema version failed!"; success = false; } @@ -161,12 +209,14 @@ bool PostgreSqlStorage::setupSchemaVersion(int version) } -UserId PostgreSqlStorage::addUser(const QString &user, const QString &password) +UserId PostgreSqlStorage::addUser(const QString &user, const QString &password, const QString &authenticator) { QSqlQuery query(logDb()); query.prepare(queryString("insert_quasseluser")); query.bindValue(":username", user); - query.bindValue(":password", cryptedPassword(password)); + query.bindValue(":password", hashPassword(password)); + query.bindValue(":hashversion", Storage::HashVersion::Latest); + query.bindValue(":authenticator", authenticator); safeExec(query); if (!watchQuery(query)) return 0; @@ -183,8 +233,10 @@ bool PostgreSqlStorage::updateUser(UserId user, const QString &password) QSqlQuery query(logDb()); query.prepare(queryString("update_userpassword")); query.bindValue(":userid", user.toInt()); - query.bindValue(":password", cryptedPassword(password)); + query.bindValue(":password", hashPassword(password)); + query.bindValue(":hashversion", Storage::HashVersion::Latest); safeExec(query); + watchQuery(query); return query.numRowsAffected() != 0; } @@ -196,6 +248,7 @@ void PostgreSqlStorage::renameUser(UserId user, const QString &newName) query.bindValue(":userid", user.toInt()); query.bindValue(":username", newName); safeExec(query); + watchQuery(query); emit userRenamed(user, newName); } @@ -205,10 +258,10 @@ UserId PostgreSqlStorage::validateUser(const QString &user, const QString &passw QSqlQuery query(logDb()); query.prepare(queryString("select_authuser")); query.bindValue(":username", user); - query.bindValue(":password", cryptedPassword(password)); safeExec(query); + watchQuery(query); - if (query.first()) { + if (query.first() && checkHashedPassword(query.value(0).toInt(), password, query.value(1).toString(), static_cast(query.value(2).toInt()))) { return query.value(0).toInt(); } else { @@ -223,6 +276,7 @@ UserId PostgreSqlStorage::getUserId(const QString &user) query.prepare(queryString("select_userid")); query.bindValue(":username", user); safeExec(query); + watchQuery(query); if (query.first()) { return query.value(0).toInt(); @@ -232,12 +286,28 @@ UserId PostgreSqlStorage::getUserId(const QString &user) } } +QString PostgreSqlStorage::getUserAuthenticator(const UserId userid) +{ + QSqlQuery query(logDb()); + query.prepare(queryString("select_authenticator")); + query.bindValue(":userid", userid.toInt()); + safeExec(query); + watchQuery(query); + + if (query.first()) { + return query.value(0).toString(); + } + else { + return QString(""); + } +} UserId PostgreSqlStorage::internalUser() { QSqlQuery query(logDb()); query.prepare(queryString("select_internaluser")); safeExec(query); + watchQuery(query); if (query.first()) { return query.value(0).toInt(); @@ -251,7 +321,7 @@ UserId PostgreSqlStorage::internalUser() void PostgreSqlStorage::delUser(UserId user) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::delUser(): cannot start transaction!"; return; } @@ -284,6 +354,7 @@ void PostgreSqlStorage::setUserSetting(UserId userId, const QString &settingName selectQuery.bindValue(":userid", userId.toInt()); selectQuery.bindValue(":settingname", settingName); safeExec(selectQuery); + watchQuery(selectQuery); QString setQueryString; if (!selectQuery.first()) { @@ -299,6 +370,7 @@ void PostgreSqlStorage::setUserSetting(UserId userId, const QString &settingName setQuery.bindValue(":settingname", settingName); setQuery.bindValue(":settingvalue", rawData); safeExec(setQuery); + watchQuery(setQuery); } @@ -309,6 +381,7 @@ QVariant PostgreSqlStorage::getUserSetting(UserId userId, const QString &setting query.bindValue(":userid", userId.toInt()); query.bindValue(":settingname", settingName); safeExec(query); + watchQuery(query); if (query.first()) { QVariant data; @@ -329,7 +402,7 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity IdentityId identityId; QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::createIdentity(): Unable to start Transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return identityId; @@ -363,8 +436,7 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity query.bindValue(":sslkey", QByteArray()); #endif safeExec(query); - if (query.lastError().isValid()) { - watchQuery(query); + if (!watchQuery(query)) { db.rollback(); return IdentityId(); } @@ -374,7 +446,6 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity identity.setId(identityId); if (!identityId.isValid()) { - watchQuery(query); db.rollback(); return IdentityId(); } @@ -403,7 +474,7 @@ IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::updateIdentity(): Unable to start Transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return false; @@ -414,6 +485,7 @@ bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity checkQuery.bindValue(":identityid", identity.id().toInt()); checkQuery.bindValue(":userid", user.toInt()); safeExec(checkQuery); + watchQuery(checkQuery); // there should be exactly one identity for the given id and user if (!checkQuery.first() || checkQuery.value(0).toInt() != 1) { @@ -488,7 +560,7 @@ bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity void PostgreSqlStorage::removeIdentity(UserId user, IdentityId identityId) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::removeIdentity(): Unable to start Transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return; @@ -527,6 +599,7 @@ QList PostgreSqlStorage::identities(UserId user) nickQuery.prepare(queryString("select_nicks")); safeExec(query); + watchQuery(query); while (query.next()) { CoreIdentity identity(IdentityId(query.value(0).toInt())); @@ -573,7 +646,7 @@ NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info) NetworkId networkId; QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::createNetwork(): failed to begin transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return false; @@ -584,8 +657,7 @@ NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info) query.bindValue(":userid", user.toInt()); bindNetworkInfo(query, info); safeExec(query); - if (query.lastError().isValid()) { - watchQuery(query); + if (!watchQuery(query)) { db.rollback(); return NetworkId(); } @@ -594,7 +666,6 @@ NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info) networkId = query.value(0).toInt(); if (!networkId.isValid()) { - watchQuery(query); db.rollback(); return NetworkId(); } @@ -641,6 +712,11 @@ void PostgreSqlStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &inf query.bindValue(":autoreconnectretries", info.autoReconnectRetries); query.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries); query.bindValue(":rejoinchannels", info.rejoinChannels); + // Custom rate limiting + query.bindValue(":usecustomessagerate", info.useCustomMessageRate); + query.bindValue(":messagerateburstsize", info.messageRateBurstSize); + query.bindValue(":messageratedelay", info.messageRateDelay); + query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate); if (info.networkId.isValid()) query.bindValue(":networkid", info.networkId.toInt()); } @@ -659,13 +735,14 @@ void PostgreSqlStorage::bindServerInfo(QSqlQuery &query, const Network::Server & query.bindValue(":proxyport", server.proxyPort); query.bindValue(":proxyuser", server.proxyUser); query.bindValue(":proxypass", server.proxyPass); + query.bindValue(":sslverify", server.sslVerify); } bool PostgreSqlStorage::updateNetwork(UserId user, const NetworkInfo &info) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::updateNetwork(): failed to begin transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return false; @@ -720,7 +797,7 @@ bool PostgreSqlStorage::updateNetwork(UserId user, const NetworkInfo &info) bool PostgreSqlStorage::removeNetwork(UserId user, const NetworkId &networkId) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::removeNetwork(): cannot start transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return false; @@ -770,9 +847,9 @@ QList PostgreSqlStorage::networks(UserId user) net.networkId = networksQuery.value(0).toInt(); net.networkName = networksQuery.value(1).toString(); net.identity = networksQuery.value(2).toInt(); - net.codecForServer = networksQuery.value(3).toString().toAscii(); - net.codecForEncoding = networksQuery.value(4).toString().toAscii(); - net.codecForDecoding = networksQuery.value(5).toString().toAscii(); + net.codecForServer = networksQuery.value(3).toString().toLatin1(); + net.codecForEncoding = networksQuery.value(4).toString().toLatin1(); + net.codecForDecoding = networksQuery.value(5).toString().toLatin1(); net.useRandomServer = networksQuery.value(6).toBool(); net.perform = networksQuery.value(7).toString().split("\n"); net.useAutoIdentify = networksQuery.value(8).toBool(); @@ -786,6 +863,11 @@ QList PostgreSqlStorage::networks(UserId user) net.useSasl = networksQuery.value(16).toBool(); net.saslAccount = networksQuery.value(17).toString(); net.saslPassword = networksQuery.value(18).toString(); + // Custom rate limiting + net.useCustomMessageRate = networksQuery.value(19).toBool(); + net.messageRateBurstSize = networksQuery.value(20).toUInt(); + net.messageRateDelay = networksQuery.value(21).toUInt(); + net.unlimitedMessageRate = networksQuery.value(22).toBool(); serversQuery.bindValue(":networkid", net.networkId.toInt()); safeExec(serversQuery); @@ -808,6 +890,7 @@ QList PostgreSqlStorage::networks(UserId user) server.proxyPort = serversQuery.value(8).toUInt(); server.proxyUser = serversQuery.value(9).toString(); server.proxyPass = serversQuery.value(10).toString(); + server.sslVerify = serversQuery.value(11).toBool(); servers << server; } net.serverList = servers; @@ -888,7 +971,7 @@ void PostgreSqlStorage::setChannelPersistent(UserId user, const NetworkId &netwo QSqlQuery query(logDb()); query.prepare(queryString("update_buffer_persistent_channel")); query.bindValue(":userid", user.toInt()); - query.bindValue(":networkId", networkId.toInt()); + query.bindValue(":networkid", networkId.toInt()); query.bindValue(":buffercname", channel.toLower()); query.bindValue(":joined", isJoined); safeExec(query); @@ -901,7 +984,7 @@ void PostgreSqlStorage::setPersistentChannelKey(UserId user, const NetworkId &ne QSqlQuery query(logDb()); query.prepare(queryString("update_buffer_set_channel_key")); query.bindValue(":userid", user.toInt()); - query.bindValue(":networkId", networkId.toInt()); + query.bindValue(":networkid", networkId.toInt()); query.bindValue(":buffercname", channel.toLower()); query.bindValue(":key", key); safeExec(query); @@ -966,7 +1049,7 @@ void PostgreSqlStorage::setUserModes(UserId user, NetworkId networkId, const QSt BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer, bool create) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::bufferInfo(): cannot start read only transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return BufferInfo(); @@ -978,6 +1061,7 @@ BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId query.bindValue(":userid", user.toInt()); query.bindValue(":buffercname", buffer.toLower()); safeExec(query); + watchQuery(query); if (query.first()) { BufferInfo bufferInfo = BufferInfo(query.value(0).toInt(), networkId, (BufferInfo::Type)query.value(1).toInt(), 0, buffer); @@ -987,7 +1071,7 @@ BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId qCritical() << " bound Values:"; QList list = query.boundValues().values(); for (int i = 0; i < list.size(); ++i) - qCritical() << i << ":" << list.at(i).toString().toAscii().data(); + qCritical() << i << ":" << list.at(i).toString().toLatin1().data(); Q_ASSERT(false); } db.commit(); @@ -1010,9 +1094,8 @@ BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId safeExec(createQuery); - if (createQuery.lastError().isValid()) { + if (!watchQuery(createQuery)) { qWarning() << "PostgreSqlStorage::bufferInfo(): unable to create buffer"; - watchQuery(createQuery); db.rollback(); return BufferInfo(); } @@ -1099,7 +1182,7 @@ QList PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, Netwo bool PostgreSqlStorage::removeBuffer(const UserId &user, const BufferId &bufferId) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::removeBuffer(): cannot start transaction!"; return false; } @@ -1134,7 +1217,7 @@ bool PostgreSqlStorage::removeBuffer(const UserId &user, const BufferId &bufferI bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferId, const QString &newName) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::renameBuffer(): cannot start transaction!"; return false; } @@ -1146,8 +1229,7 @@ bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferI query.bindValue(":userid", user.toInt()); query.bindValue(":bufferid", bufferId.toInt()); safeExec(query); - if (query.lastError().isValid()) { - watchQuery(query); + if (!watchQuery(query)) { db.rollback(); return false; } @@ -1172,7 +1254,7 @@ bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferI bool PostgreSqlStorage::mergeBuffersPermanently(const UserId &user, const BufferId &bufferId1, const BufferId &bufferId2) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::mergeBuffersPermanently(): cannot start transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return false; @@ -1304,10 +1386,64 @@ QHash PostgreSqlStorage::bufferMarkerLineMsgIds(UserId user) } +void PostgreSqlStorage::setBufferActivity(UserId user, const BufferId &bufferId, const Message::Types &bufferActivity) +{ + QSqlQuery query(logDb()); + query.prepare(queryString("update_buffer_bufferactivity")); + + query.bindValue(":userid", user.toInt()); + query.bindValue(":bufferid", bufferId.toInt()); + query.bindValue(":bufferactivity", (int) bufferActivity); + safeExec(query); + watchQuery(query); +} + +QHash PostgreSqlStorage::bufferActivities(UserId user) +{ + QHash bufferActivityHash; + + QSqlDatabase db = logDb(); + if (!beginReadOnlyTransaction(db)) { + qWarning() << "PostgreSqlStorage::bufferActivities(): cannot start read only transaction!"; + qWarning() << " -" << qPrintable(db.lastError().text()); + return bufferActivityHash; + } + + QSqlQuery query(db); + query.prepare(queryString("select_buffer_bufferactivities")); + query.bindValue(":userid", user.toInt()); + safeExec(query); + if (!watchQuery(query)) { + db.rollback(); + return bufferActivityHash; + } + + while (query.next()) { + bufferActivityHash[query.value(0).toInt()] = Message::Types(query.value(1).toInt()); + } + + db.commit(); + return bufferActivityHash; +} + +Message::Types PostgreSqlStorage::bufferActivity(BufferId &bufferId, const MsgId &lastSeenMsgId) +{ + QSqlQuery query(logDb()); + query.prepare(queryString("select_buffer_bufferactivity")); + query.bindValue(":bufferid", bufferId.toInt()); + query.bindValue(":lastseenmsgid", lastSeenMsgId.toInt()); + safeExec(query); + watchQuery(query); + Message::Types result = Message::Types(0); + if (query.first()) + result = Message::Types(query.value(0).toInt()); + return result; +} + bool PostgreSqlStorage::logMessage(Message &msg) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return false; @@ -1326,7 +1462,8 @@ bool PostgreSqlStorage::logMessage(Message &msg) if (addSenderQuery.lastError().isValid()) { rollbackSavePoint("sender_sp1", db); - getSenderIdQuery = db.exec(getSenderIdQuery.lastQuery()); + getSenderIdQuery = executePreparedQuery("select_senderid", msg.sender(), db); + watchQuery(getSenderIdQuery); getSenderIdQuery.first(); senderId = getSenderIdQuery.value(0).toInt(); } @@ -1343,6 +1480,7 @@ bool PostgreSqlStorage::logMessage(Message &msg) << msg.type() << (int)msg.flags() << senderId + << msg.senderPrefixes() << msg.contents(); QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db); @@ -1367,7 +1505,7 @@ bool PostgreSqlStorage::logMessage(Message &msg) bool PostgreSqlStorage::logMessages(MessageList &msgs) { QSqlDatabase db = logDb(); - if (!db.transaction()) { + if (!beginTransaction(db)) { qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!"; qWarning() << " -" << qPrintable(db.lastError().text()); return false; @@ -1395,7 +1533,8 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs) if (addSenderQuery.lastError().isValid()) { // seems it was inserted meanwhile... by a different thread rollbackSavePoint("sender_sp", db); - selectSenderQuery = db.exec(selectSenderQuery.lastQuery()); + selectSenderQuery = executePreparedQuery("select_senderid", sender, db); + watchQuery(selectSenderQuery); selectSenderQuery.first(); senderIdList << selectSenderQuery.value(0).toInt(); senderIds[sender] = selectSenderQuery.value(0).toInt(); @@ -1419,6 +1558,7 @@ bool PostgreSqlStorage::logMessages(MessageList &msgs) << msg.type() << (int)msg.flags() << senderIdList.at(i) + << msg.senderPrefixes() << msg.contents(); QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db); if (!watchQuery(logMessageQuery)) { @@ -1465,7 +1605,7 @@ QList PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms QString queryName; QVariantList params; if (last == -1 && first == -1) { - queryName = "select_messages"; + queryName = "select_messagesNewestK"; } else if (last == -1) { queryName = "select_messagesNewerThan"; @@ -1480,7 +1620,7 @@ QList PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms if (limit != -1) params << limit; else - params << "ALL"; + params << QVariant(QVariant::Int); QSqlQuery query = executePreparedQuery(queryName, params, db); @@ -1497,8 +1637,9 @@ QList PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, Ms Message msg(timestamp, bufferInfo, (Message::Type)query.value(2).toUInt(), - query.value(5).toString(), + query.value(6).toString(), query.value(4).toString(), + query.value(5).toString(), (Message::Flags)query.value(3).toUInt()); msg.setMsgId(query.value(0).toInt()); messagelist << msg; @@ -1549,8 +1690,9 @@ QList PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId Message msg(timestamp, bufferInfoHash[query.value(1).toInt()], (Message::Type)query.value(3).toUInt(), - query.value(6).toString(), + query.value(7).toString(), query.value(5).toString(), + query.value(6).toString(), (Message::Flags)query.value(4).toUInt()); msg.setMsgId(query.value(0).toInt()); messagelist << msg; @@ -1567,7 +1709,7 @@ QList PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId // qDebug() << " bound Values:"; // QList list = query.boundValues().values(); // for (int i = 0; i < list.size(); ++i) -// qCritical() << i << ": " << list.at(i).toString().toAscii().data(); +// qCritical() << i << ": " << list.at(i).toString().toLatin1().data(); // query.exec(); @@ -1584,14 +1726,29 @@ QList PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId // return; // } + +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, const QSqlDatabase &db) +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 @@ -1605,10 +1762,22 @@ QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, co query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring)); } - if (db.lastError().isValid()) { - // and once again: Qt leaves us without error codes so we either parse (language dependant(!)) strings + 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. :) - db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query"); 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) { @@ -1619,7 +1788,7 @@ QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, co return QSqlQuery(db); } } - // we alwas execute the query again, even if the query was already prepared. + // 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) @@ -1638,7 +1807,7 @@ QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, co } -QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList ¶ms, const QSqlDatabase &db) +QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList ¶ms, QSqlDatabase &db) { QSqlDriver *driver = db.driver(); @@ -1664,7 +1833,7 @@ QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, cons } -QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant ¶m, const QSqlDatabase &db) +QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant ¶m, QSqlDatabase &db) { QSqlField field; field.setType(param.type()); @@ -1684,6 +1853,26 @@ void PostgreSqlStorage::deallocateQuery(const QString &queryname, const QSqlData } +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 i(query.boundValues()); + while (i.hasNext()) + { + i.next(); + retryQuery.bindValue(i.key(),i.value()); + } + query = retryQuery; + query.exec(); + } +} + // ======================================== // PostgreSqlMigrationWriter // ======================================== @@ -1737,6 +1926,8 @@ bool PostgreSqlMigrationWriter::writeMo(const QuasselUserMO &user) bindValue(0, user.id.toInt()); bindValue(1, user.username); bindValue(2, user.password); + bindValue(3, user.hashversion); + bindValue(4, user.authenticator); return exec(); } @@ -1820,6 +2011,11 @@ bool PostgreSqlMigrationWriter::writeMo(const NetworkMO &network) bindValue(22, network.usesasl); bindValue(23, network.saslaccount); bindValue(24, network.saslpassword); + // Custom rate limiting + bindValue(25, network.usecustommessagerate); + bindValue(26, network.messagerateburstsize); + bindValue(27, network.messageratedelay); + bindValue(28, network.unlimitedmessagerate); return exec(); } @@ -1834,10 +2030,12 @@ bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer) 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); + bindValue(7, buffer.lastmsgid); + bindValue(8, buffer.lastseenmsgid); + bindValue(9, buffer.markerlinemsgid); + bindValue(10, buffer.bufferactivity); + bindValue(11, buffer.key); + bindValue(12, buffer.joined); return exec(); } @@ -1851,7 +2049,8 @@ bool PostgreSqlMigrationWriter::writeMo(const BacklogMO &backlog) bindValue(3, backlog.type); bindValue(4, (int)backlog.flags); bindValue(5, backlog.senderid); - bindValue(6, backlog.message); + bindValue(6, backlog.senderprefixes); + bindValue(7, backlog.message); return exec(); } @@ -1873,6 +2072,7 @@ bool PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver) bindValue(11, ircserver.proxyport); bindValue(12, ircserver.proxyuser); bindValue(13, ircserver.proxypass); + bindValue(14, ircserver.sslverify); return exec(); } @@ -1900,11 +2100,17 @@ bool PostgreSqlMigrationWriter::postProcess() << Sequence("quasseluser", "userid") << Sequence("sender", "senderid"); QList::const_iterator iter; - for (iter = sequences.constBegin(); iter != sequences.constEnd(); 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; } + + // Update the lastmsgid for all existing buffers. + resetQuery(); + newQuery(QString("SELECT populate_lastmsgid()"), db); + if (!exec()) + return false; return true; }