/***************************************************************************
- * Copyright (C) 2005-2019 by the Quassel Project *
+ * Copyright (C) 2005-2020 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
#include "postgresqlstorage.h"
-#include <QtSql>
+#include <QByteArray>
+#include <QDataStream>
+#include <QSqlDriver>
+#include <QSqlField>
#include "network.h"
#include "quassel.h"
{
if (!QSqlDatabase::isDriverAvailable("QPSQL")) {
qWarning() << qPrintable(tr("PostgreSQL driver plugin not available for Qt. Installed drivers:"))
- << qPrintable(QSqlDatabase::drivers().join(", "));
+ << qPrintable(QSqlDatabase::drivers().join(", "));
return false;
}
return true;
break;
default:
// The slash got replaced with 0 or more than 2 slashes! o_O
- qCritical() << "Your version of Qt does something _VERY_ strange to slashes in QSqlQueries! You should consult your trusted doctor!";
+ qCritical()
+ << "Your version of Qt does something _VERY_ strange to slashes in QSqlQueries! You should consult your trusted doctor!";
return false;
break;
}
return AbstractSqlStorage::installedSchemaVersion();
}
-bool PostgreSqlStorage::updateSchemaVersion(int newVersion)
+bool PostgreSqlStorage::updateSchemaVersion(int newVersion, bool clearUpgradeStep)
{
- QSqlQuery query(logDb());
+ // Atomically update the schema version and clear the upgrade step, if specified
+ // Note: This will need reworked if "updateSchemaVersion" is ever called within a transaction.
+ QSqlDatabase db = logDb();
+ if (!beginTransaction(db)) {
+ qWarning() << "PostgreSqlStorage::updateSchemaVersion(int, bool): cannot start transaction!";
+ qWarning() << " -" << qPrintable(db.lastError().text());
+ return false;
+ }
+
+ QSqlQuery query(db);
query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'");
query.bindValue(":version", newVersion);
safeExec(query);
- bool success = true;
if (!watchQuery(query)) {
- qCritical() << "PostgreSqlStorage::updateSchemaVersion(int): Updating schema version failed!";
- success = false;
+ qCritical() << "PostgreSqlStorage::updateSchemaVersion(int, bool): Updating schema version failed!";
+ db.rollback();
+ return false;
}
- return success;
+
+ if (clearUpgradeStep) {
+ // Try clearing the upgrade step if requested
+ if (!setSchemaVersionUpgradeStep("")) {
+ db.rollback();
+ return false;
+ }
+ }
+
+ // Successful, commit and return true
+ db.commit();
+ return true;
}
bool PostgreSqlStorage::setupSchemaVersion(int version)
return success;
}
+QString PostgreSqlStorage::schemaVersionUpgradeStep()
+{
+ QSqlQuery query(logDb());
+ query.prepare("SELECT value FROM coreinfo WHERE key = 'schemaupgradestep'");
+ safeExec(query);
+ watchQuery(query);
+ if (query.first())
+ return query.value(0).toString();
+
+ // Fall back to the default value
+ return AbstractSqlStorage::schemaVersionUpgradeStep();
+}
+
+bool PostgreSqlStorage::setSchemaVersionUpgradeStep(QString upgradeQuery)
+{
+ // Intentionally do not wrap in a transaction so other functions can include multiple operations
+
+ QSqlQuery query(logDb());
+ query.prepare("UPDATE coreinfo SET value = :upgradestep WHERE key = 'schemaupgradestep'");
+ query.bindValue(":upgradestep", upgradeQuery);
+ safeExec(query);
+
+ // Make sure that the query didn't fail (shouldn't ever happen), and that some non-zero number
+ // of rows were affected
+ bool success = watchQuery(query) && query.numRowsAffected() != 0;
+
+ if (!success) {
+ // The key might not exist (Quassel 0.13.0 and older). Try inserting it...
+ query = QSqlQuery(logDb());
+ query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaupgradestep', :upgradestep)");
+ query.bindValue(":upgradestep", upgradeQuery);
+ safeExec(query);
+
+ if (!watchQuery(query)) {
+ qCritical() << Q_FUNC_INFO << "Setting schema upgrade step failed!";
+ success = false;
+ }
+ else {
+ success = true;
+ }
+ }
+ return success;
+}
+
UserId PostgreSqlStorage::addUser(const QString& user, const QString& password, const QString& authenticator)
{
QSqlQuery query(logDb());
query.bindValue(":kickreason", identity.kickReason());
query.bindValue(":partreason", identity.partReason());
query.bindValue(":quitreason", identity.quitReason());
-#ifdef HAVE_SSL
query.bindValue(":sslcert", identity.sslCert().toPem());
query.bindValue(":sslkey", identity.sslKey().toPem());
-#else
- query.bindValue(":sslcert", QByteArray());
- query.bindValue(":sslkey", QByteArray());
-#endif
safeExec(query);
if (!watchQuery(query)) {
db.rollback();
query.bindValue(":kickreason", identity.kickReason());
query.bindValue(":partreason", identity.partReason());
query.bindValue(":quitreason", identity.quitReason());
-#ifdef HAVE_SSL
query.bindValue(":sslcert", identity.sslCert().toPem());
query.bindValue(":sslkey", identity.sslKey().toPem());
-#else
- query.bindValue(":sslcert", QByteArray());
- query.bindValue(":sslkey", QByteArray());
-#endif
query.bindValue(":identityid", identity.id().toInt());
safeExec(query);
}
}
-QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
+std::vector<CoreIdentity> PostgreSqlStorage::identities(UserId user)
{
- QList<CoreIdentity> identities;
+ std::vector<CoreIdentity> identities;
QSqlDatabase db = logDb();
if (!beginReadOnlyTransaction(db)) {
identity.setKickReason(query.value(15).toString());
identity.setPartReason(query.value(16).toString());
identity.setQuitReason(query.value(17).toString());
-#ifdef HAVE_SSL
identity.setSslCert(query.value(18).toByteArray());
identity.setSslKey(query.value(19).toByteArray());
-#endif
nickQuery.bindValue(":identityid", identity.id().toInt());
QList<QString> nicks;
nicks << nickQuery.value(0).toString();
}
identity.setNicks(nicks);
- identities << identity;
+ identities.push_back(std::move(identity));
}
db.commit();
return identities;
query.bindValue(":messagerateburstsize", info.messageRateBurstSize);
query.bindValue(":messageratedelay", info.messageRateDelay);
query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate);
+ query.bindValue(":skipcaps", info.skipCapsToString());
+
if (info.networkId.isValid())
query.bindValue(":networkid", info.networkId.toInt());
}
return true;
}
-QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
+std::vector<NetworkInfo> PostgreSqlStorage::networks(UserId user)
{
- QList<NetworkInfo> nets;
+ std::vector<NetworkInfo> nets;
QSqlDatabase db = logDb();
if (!beginReadOnlyTransaction(db)) {
net.messageRateBurstSize = networksQuery.value(20).toUInt();
net.messageRateDelay = networksQuery.value(21).toUInt();
net.unlimitedMessageRate = networksQuery.value(22).toBool();
+ net.skipCapsFromString(networksQuery.value(23).toString());
serversQuery.bindValue(":networkid", net.networkId.toInt());
safeExec(serversQuery);
servers << server;
}
net.serverList = servers;
- nets << net;
+ nets.push_back(std::move(net));
}
db.commit();
return nets;
}
-QList<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
+std::vector<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
{
- QList<NetworkId> connectedNets;
+ std::vector<NetworkId> connectedNets;
QSqlDatabase db = logDb();
if (!beginReadOnlyTransaction(db)) {
watchQuery(query);
while (query.next()) {
- connectedNets << query.value(0).toInt();
+ connectedNets.emplace_back(query.value(0).toInt());
}
db.commit();
return bufferInfo;
}
-QList<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
+std::vector<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
{
- QList<BufferInfo> bufferlist;
+ std::vector<BufferInfo> bufferlist;
QSqlDatabase db = logDb();
if (!beginReadOnlyTransaction(db)) {
safeExec(query);
watchQuery(query);
while (query.next()) {
- bufferlist << BufferInfo(query.value(0).toInt(),
- query.value(1).toInt(),
- (BufferInfo::Type)query.value(2).toInt(),
- query.value(3).toInt(),
- query.value(4).toString());
+ bufferlist.emplace_back(query.value(0).toInt(),
+ query.value(1).toInt(),
+ (BufferInfo::Type)query.value(2).toInt(),
+ query.value(3).toInt(),
+ query.value(4).toString());
}
db.commit();
return bufferlist;
}
-QList<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
+std::vector<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
{
- QList<BufferId> bufferList;
+ std::vector<BufferId> bufferList;
QSqlDatabase db = logDb();
if (!beginReadOnlyTransaction(db)) {
safeExec(query);
watchQuery(query);
while (query.next()) {
- bufferList << BufferId(query.value(0).toInt());
+ bufferList.emplace_back(query.value(0).toInt());
}
db.commit();
return bufferList;
return true;
}
+QHash<BufferId, MsgId> PostgreSqlStorage::bufferLastMsgIds(UserId user)
+{
+ QHash<BufferId, MsgId> lastMsgHash;
+
+ QSqlDatabase db = logDb();
+ if (!beginReadOnlyTransaction(db)) {
+ qWarning() << "PostgreSqlStorage::bufferLastMsgIds(): cannot start read only transaction!";
+ qWarning() << " -" << qPrintable(db.lastError().text());
+ return lastMsgHash;
+ }
+
+ QSqlQuery query(db);
+ query.prepare(queryString("select_buffer_last_messages"));
+ query.bindValue(":userid", user.toInt());
+ safeExec(query);
+ if (!watchQuery(query)) {
+ db.rollback();
+ return lastMsgHash;
+ }
+
+ while (query.next()) {
+ lastMsgHash[query.value(0).toInt()] = query.value(1).toLongLong();
+ }
+
+ db.commit();
+ return lastMsgHash;
+}
+
void PostgreSqlStorage::setBufferLastSeenMsg(UserId user, const BufferId& bufferId, const MsgId& msgId)
{
QSqlQuery query(logDb());
return true;
}
-QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
+std::vector<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
{
- QList<Message> messagelist;
+ std::vector<Message> messagelist;
QSqlDatabase db = logDb();
if (!beginReadOnlyTransaction(db)) {
query.value(7).toString(),
(Message::Flags)query.value(3).toInt());
msg.setMsgId(query.value(0).toLongLong());
- messagelist << msg;
+ messagelist.push_back(std::move(msg));
}
db.commit();
return messagelist;
}
-QList<Message> PostgreSqlStorage::requestMsgsFiltered(
+std::vector<Message> PostgreSqlStorage::requestMsgsFiltered(
UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
{
- QList<Message> messagelist;
+ std::vector<Message> messagelist;
QSqlDatabase db = logDb();
if (!beginReadOnlyTransaction(db)) {
query.value(7).toString(),
Message::Flags{query.value(3).toInt()});
msg.setMsgId(query.value(0).toLongLong());
- messagelist << msg;
+ messagelist.push_back(std::move(msg));
+ }
+
+ db.commit();
+ return messagelist;
+}
+
+std::vector<Message> PostgreSqlStorage::requestMsgsForward(
+ UserId user, BufferId bufferId, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
+{
+ std::vector<Message> messagelist;
+
+ QSqlDatabase db = logDb();
+ if (!beginReadOnlyTransaction(db)) {
+ qWarning() << "PostgreSqlStorage::requestMsgsForward(): cannot start read only transaction!";
+ qWarning() << " -" << qPrintable(db.lastError().text());
+ return messagelist;
+ }
+
+ BufferInfo bufferInfo = getBufferInfo(user, bufferId);
+ if (!bufferInfo.isValid()) {
+ db.rollback();
+ return messagelist;
+ }
+
+ QString queryName;
+ QVariantList params;
+
+ if (first == -1) {
+ params << std::numeric_limits<qint64>::min();
+ } else {
+ params << first.toQint64();
+ }
+
+ if (last == -1) {
+ params << std::numeric_limits<qint64>::max();
+ } else {
+ params << last.toQint64();
+ }
+
+ params << bufferId.toInt();
+
+ int typeRaw = type;
+ int flagsRaw = flags;
+ params << typeRaw;
+ params << flagsRaw;
+
+ if (limit != -1)
+ params << limit;
+ else
+ params << QVariant(QVariant::Int);
+
+ QSqlQuery query = executePreparedQuery("select_messagesForward", params, db);
+
+ if (!watchQuery(query)) {
+ qDebug() << "select_messages failed";
+ db.rollback();
+ return messagelist;
+ }
+
+ QDateTime timestamp;
+ while (query.next()) {
+ // PostgreSQL returns date/time in ISO 8601 format, no 64-bit handling needed
+ // See https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-DATETIME-OUTPUT
+ timestamp = query.value(1).toDateTime();
+ timestamp.setTimeSpec(Qt::UTC);
+ Message msg(timestamp,
+ bufferInfo,
+ (Message::Type)query.value(2).toInt(),
+ query.value(8).toString(),
+ query.value(4).toString(),
+ query.value(5).toString(),
+ query.value(6).toString(),
+ query.value(7).toString(),
+ (Message::Flags)query.value(3).toInt());
+ msg.setMsgId(query.value(0).toLongLong());
+ messagelist.push_back(std::move(msg));
}
db.commit();
return messagelist;
}
-QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
+std::vector<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
{
- QList<Message> messagelist;
+ std::vector<Message> messagelist;
// requestBuffers uses it's own transaction.
QHash<BufferId, BufferInfo> bufferInfoHash;
query.value(8).toString(),
(Message::Flags)query.value(4).toInt());
msg.setMsgId(query.value(0).toLongLong());
- messagelist << msg;
+ messagelist.push_back(std::move(msg));
}
db.commit();
return messagelist;
}
-QList<Message> PostgreSqlStorage::requestAllMsgsFiltered(
+std::vector<Message> PostgreSqlStorage::requestAllMsgsFiltered(
UserId user, MsgId first, MsgId last, int limit, Message::Types type, Message::Flags flags)
{
- QList<Message> messagelist;
+ std::vector<Message> messagelist;
// requestBuffers uses it's own transaction.
QHash<BufferId, BufferInfo> bufferInfoHash;
query.value(8).toString(),
Message::Flags{query.value(4).toInt()});
msg.setMsgId(query.value(0).toLongLong());
- messagelist << msg;
+ messagelist.push_back(std::move(msg));
}
db.commit();
bindValue(26, network.messagerateburstsize);
bindValue(27, network.messageratedelay);
bindValue(28, network.unlimitedmessagerate);
+ // Skipped IRCv3 caps
+ bindValue(29, network.skipcaps);
return exec();
}