Merge pull request #8561 from nextcloud/bugfix/dbquota

fix(quota): return unlimited free space for type changes.
This commit is contained in:
Matthieu Gallien 2025-08-13 11:02:32 +02:00 committed by GitHub
commit decf1e7c3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 120 additions and 10 deletions

View File

@ -712,9 +712,40 @@ bool SyncJournalDb::updateDatabaseStructure()
return true;
}
bool SyncJournalDb::hasDefaultValue(const QString &columnName)
{
SqlQuery query(_db);
const auto selectDefault = QStringLiteral("SELECT dflt_value FROM pragma_table_info('metadata') WHERE name = '%1';").arg(columnName);
query.prepare(selectDefault.toLatin1());
if (!query.exec()) {
sqlFail(QStringLiteral("check default value for: %1").arg(columnName), query);
return false;
}
if (const auto result = query.next();!result.ok || !result.hasData) {
qCWarning(lcDb) << "database error:" << query.error();
return false;
}
return !query.nullValue(0);
}
bool SyncJournalDb::removeColumn(const QString &columnName)
{
SqlQuery query(_db);
const auto request = QStringLiteral("ALTER TABLE metadata DROP COLUMN %1;").arg(columnName);
query.prepare(request.toLatin1());
if (!query.exec()) {
sqlFail(QStringLiteral("update metadata structure: drop %1 column").arg(columnName), query);
return false;
}
commitInternal(QStringLiteral("update database structure: drop %1 column").arg(columnName));
return true;
}
bool SyncJournalDb::updateMetadataTableStructure()
{
auto columns = tableColumns("metadata");
bool re = true;
@ -723,11 +754,18 @@ bool SyncJournalDb::updateMetadataTableStructure()
return false;
}
const auto addColumn = [this, &columns, &re] (const QString &columnName, const QString &dataType, const bool withIndex = false) {
const auto latin1ColumnName = columnName.toLatin1();
if (columns.indexOf(latin1ColumnName) == -1) {
const auto columnExists = [&columns] (const QString &columnName) -> bool {
return columns.indexOf(columnName.toLatin1()) > -1;
};
const auto addColumn = [this, &re, &columnExists] (const QString &columnName, const QString &dataType, const bool withIndex = false, const QString defaultCommand = {}) {
if (!columnExists(columnName)) {
SqlQuery query(_db);
const auto request = QStringLiteral("ALTER TABLE metadata ADD COLUMN %1 %2;").arg(columnName).arg(dataType);
auto request = QStringLiteral("ALTER TABLE metadata ADD COLUMN %1 %2").arg(columnName).arg(dataType);
if (!defaultCommand.isEmpty()) {
request.append(QStringLiteral(" ") + defaultCommand);
}
request.append(QStringLiteral(";"));
query.prepare(request.toLatin1());
if (!query.exec()) {
sqlFail(QStringLiteral("updateMetadataTableStructure: add %1 column").arg(columnName), query);
@ -851,8 +889,29 @@ bool SyncJournalDb::updateMetadataTableStructure()
addColumn(QStringLiteral("isLivePhoto"), QStringLiteral("INTEGER"));
addColumn(QStringLiteral("livePhotoFile"), QStringLiteral("TEXT"));
addColumn(QStringLiteral("quotaBytesUsed"), QStringLiteral("BIGINT"));
addColumn(QStringLiteral("quotaBytesAvailable"), QStringLiteral("BIGINT"));
{
const auto quotaBytesUsed = QStringLiteral("quotaBytesUsed");
const auto quotaBytesAvailable = QStringLiteral("quotaBytesAvailable");
const auto defaultCommand = QStringLiteral("DEFAULT -1 NOT NULL");
const auto bigInt = QStringLiteral("BIGINT");
auto result = false;
if (columnExists(quotaBytesUsed) && !hasDefaultValue(quotaBytesUsed)) {
result = removeColumn(quotaBytesUsed);
}
if (columnExists(quotaBytesAvailable) && !hasDefaultValue(quotaBytesAvailable)) {
result = removeColumn(quotaBytesAvailable);
}
if (result) {
columns = tableColumns("metadata");
}
addColumn(quotaBytesUsed, bigInt, false, defaultCommand);
addColumn(quotaBytesAvailable, bigInt, false, defaultCommand);
}
return re;
}

View File

@ -21,6 +21,8 @@
#include "common/result.h"
#include "common/pinstate.h"
class TestSyncJournalDB;
namespace OCC {
class SyncJournalFileRecord;
@ -399,6 +401,8 @@ private:
[[nodiscard]] bool updateDatabaseStructure();
[[nodiscard]] bool updateMetadataTableStructure();
[[nodiscard]] bool updateErrorBlacklistTableStructure();
[[nodiscard]] bool removeColumn(const QString &columnName);
[[nodiscard]] bool hasDefaultValue(const QString &columnName);
bool sqlFail(const QString &log, const SqlQuery &query);
void commitInternal(const QString &context, bool startTrans = true);
void startTransaction();
@ -444,6 +448,8 @@ private:
QByteArray _journalMode;
PreparedSqlQueryManager _queryManager;
friend class ::TestSyncJournalDB;
};
bool OCSYNC_EXPORT

View File

@ -1085,7 +1085,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it
int64_t ProcessDirectoryJob::folderBytesAvailable(const SyncFileItemPtr &item, const FolderQuota::ServerEntry serverEntry) const
{
const auto unlimitedFreeSpace = -3;
if (item->_size == 0 || item->_direction != SyncFileItem::Up || item->isDirectory()) {
if (item->_size == 0 || item->_direction != SyncFileItem::Up || item->isDirectory() || item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) {
return unlimitedFreeSpace;
}
@ -1093,8 +1093,7 @@ int64_t ProcessDirectoryJob::folderBytesAvailable(const SyncFileItemPtr &item, c
return unlimitedFreeSpace;
}
if (item->_instruction != CSYNC_INSTRUCTION_TYPE_CHANGE
&& (serverEntry == FolderQuota::ServerEntry::Valid || !_dirItem)) {
if (serverEntry == FolderQuota::ServerEntry::Valid || !_dirItem) {
return _folderQuota.bytesAvailable;
}

View File

@ -98,6 +98,52 @@ private slots:
QVERIFY(!record.isValid());
}
void testFolderQuota()
{
const auto bigFolderRecord = QByteArray("bigfolder");
SyncJournalFileRecord record;
record._path = bigFolderRecord;
record._inode = std::numeric_limits<quint32>::max() + 12ull;
record._modtime = dropMsecs(QDateTime::currentDateTime());
record._type = ItemTypeDirectory;
record._etag = "123123";
record._fileId = "abcd";
record._fileSize = 213089999;
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord;
QVERIFY(_db.getFileRecord(bigFolderRecord, &storedRecord));
QVERIFY(storedRecord == record);
// default values
QCOMPARE(storedRecord._folderQuota.bytesAvailable, -1);
QCOMPARE(storedRecord._folderQuota.bytesUsed, -1);
record._folderQuota.bytesUsed = 100;
record._folderQuota.bytesAvailable = 5000;
QVERIFY(_db.setFileRecord(record));
QVERIFY(_db.getFileRecord(bigFolderRecord, &storedRecord));
QVERIFY(storedRecord == record);
QCOMPARE(storedRecord._folderQuota.bytesAvailable, 5000);
QCOMPARE(storedRecord._folderQuota.bytesUsed, 100);
}
void testFolderMigration() {
const auto quotaBytesUsed = QStringLiteral("quotaBytesUsed");
const auto quotaBytesAvailable = QStringLiteral("quotaBytesAvailable");
const auto metadata = QStringLiteral("metadata");
const auto columnsBeforeRemoval = _db.tableColumns(metadata.toLatin1());
QCOMPARE_GT(columnsBeforeRemoval.indexOf(quotaBytesUsed.toLatin1()), -1);
QCOMPARE_GT(columnsBeforeRemoval.indexOf(quotaBytesAvailable.toLatin1()), -1);
QVERIFY(_db.removeColumn(quotaBytesAvailable));
QVERIFY(_db.removeColumn(quotaBytesUsed));
QVERIFY(_db.updateMetadataTableStructure());
const auto columnsAfterConnect = _db.tableColumns(metadata.toLatin1());
QVERIFY(columnsAfterConnect.indexOf(quotaBytesUsed.toLatin1()) > -1);
QVERIFY(columnsAfterConnect.indexOf(quotaBytesAvailable.toLatin1()) > -1);
QVERIFY(_db.hasDefaultValue(quotaBytesUsed));
QVERIFY(_db.hasDefaultValue(quotaBytesAvailable));
}
void testFileRecordChecksum()
{
// Try with and without a checksum