fix: forum users tables migrations

This commit is contained in:
2026-01-07 22:25:24 +02:00
parent 9f904a7e48
commit 8b489b9cc3
5 changed files with 126 additions and 29 deletions

View File

@@ -31,7 +31,6 @@ use OCP\AppFramework\Db\Entity;
* @method void setUpdatedAt(int $updatedAt)
*/
class ForumUser extends Entity implements JsonSerializable {
public $id;
protected string $userId = '';
protected int $postCount = 0;
protected int $threadCount = 0;

View File

@@ -125,7 +125,8 @@ class SeedHelper {
/**
* Create the forum_users table from scratch
* This mirrors the schema from Version1 + Version8 migrations
* This mirrors the final schema from Version1 + Version2 + Version8 migrations
* (id as primary key, user_id as unique, includes signature column)
*/
private static function createForumUsersTable(\OCP\IDBConnection $db): void {
$platform = $db->getDatabasePlatform();
@@ -138,6 +139,7 @@ class SeedHelper {
// MySQL and MariaDB both extend MySQLPlatform
$db->executeStatement("
CREATE TABLE `{$tableName}` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(64) NOT NULL,
`post_count` INT UNSIGNED NOT NULL DEFAULT 0,
`thread_count` INT UNSIGNED NOT NULL DEFAULT 0,
@@ -146,14 +148,17 @@ class SeedHelper {
`signature` TEXT DEFAULT NULL,
`created_at` INT UNSIGNED NOT NULL,
`updated_at` INT UNSIGNED NOT NULL,
PRIMARY KEY (`user_id`),
PRIMARY KEY (`id`),
UNIQUE INDEX `forum_users_user_id_uniq` (`user_id`),
INDEX `forum_users_post_count_idx` (`post_count`),
INDEX `forum_users_thread_count_idx` (`thread_count`),
INDEX `forum_users_deleted_at_idx` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
");
} elseif ($platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform) {
$db->executeStatement("
CREATE TABLE \"{$tableName}\" (
\"id\" BIGSERIAL PRIMARY KEY,
\"user_id\" VARCHAR(64) NOT NULL,
\"post_count\" INTEGER NOT NULL DEFAULT 0,
\"thread_count\" INTEGER NOT NULL DEFAULT 0,
@@ -161,16 +166,18 @@ class SeedHelper {
\"deleted_at\" INTEGER DEFAULT NULL,
\"signature\" TEXT DEFAULT NULL,
\"created_at\" INTEGER NOT NULL,
\"updated_at\" INTEGER NOT NULL,
PRIMARY KEY (\"user_id\")
\"updated_at\" INTEGER NOT NULL
)
");
$db->executeStatement("CREATE UNIQUE INDEX \"forum_users_user_id_uniq\" ON \"{$tableName}\" (\"user_id\")");
$db->executeStatement("CREATE INDEX \"forum_users_post_count_idx\" ON \"{$tableName}\" (\"post_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_thread_count_idx\" ON \"{$tableName}\" (\"thread_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_deleted_at_idx\" ON \"{$tableName}\" (\"deleted_at\")");
} else {
// SQLite (and any other platform as fallback)
$db->executeStatement("
CREATE TABLE \"{$tableName}\" (
\"id\" INTEGER PRIMARY KEY AUTOINCREMENT,
\"user_id\" VARCHAR(64) NOT NULL,
\"post_count\" INTEGER NOT NULL DEFAULT 0,
\"thread_count\" INTEGER NOT NULL DEFAULT 0,
@@ -178,11 +185,12 @@ class SeedHelper {
\"deleted_at\" INTEGER DEFAULT NULL,
\"signature\" TEXT DEFAULT NULL,
\"created_at\" INTEGER NOT NULL,
\"updated_at\" INTEGER NOT NULL,
PRIMARY KEY (\"user_id\")
\"updated_at\" INTEGER NOT NULL
)
");
$db->executeStatement("CREATE UNIQUE INDEX \"forum_users_user_id_uniq\" ON \"{$tableName}\" (\"user_id\")");
$db->executeStatement("CREATE INDEX \"forum_users_post_count_idx\" ON \"{$tableName}\" (\"post_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_thread_count_idx\" ON \"{$tableName}\" (\"thread_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_deleted_at_idx\" ON \"{$tableName}\" (\"deleted_at\")");
}
}

View File

@@ -85,12 +85,30 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
$table->addIndex(['name'], 'forum_roles_name_idx');
}
/**
* Create forum_users table (formerly forum_user_stats)
* Note: On fresh installs, this creates forum_users directly with the final schema.
* For progressive installs where forum_user_stats already exists,
* SeedHelper::ensureForumUsersTable() handles the rename.
*
* The table structure matches what Version2 transforms it to:
* - id: auto-increment primary key
* - user_id: unique string
* - signature: added in Version8
*/
private function createUserStatsTable(ISchemaWrapper $schema): void {
if ($schema->hasTable('forum_user_stats')) {
// Skip if either table already exists (handles both fresh and progressive installs)
if ($schema->hasTable('forum_users') || $schema->hasTable('forum_user_stats')) {
return;
}
$table = $schema->createTable('forum_user_stats');
// Create forum_users directly with the final schema (matching Version2's transformation)
$table = $schema->createTable('forum_users');
$table->addColumn('id', 'bigint', [
'autoincrement' => true,
'notnull' => true,
'unsigned' => true,
]);
$table->addColumn('user_id', 'string', [
'notnull' => true,
'length' => 64,
@@ -115,6 +133,10 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
'unsigned' => true,
'default' => null,
]);
$table->addColumn('signature', 'text', [
'notnull' => false,
'default' => null,
]);
$table->addColumn('created_at', 'integer', [
'notnull' => true,
'unsigned' => true,
@@ -123,9 +145,11 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
'notnull' => true,
'unsigned' => true,
]);
$table->setPrimaryKey(['user_id']);
$table->addIndex(['post_count'], 'user_stats_post_count_idx');
$table->addIndex(['deleted_at'], 'user_stats_deleted_at_idx');
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['user_id'], 'forum_users_user_id_uniq');
$table->addIndex(['post_count'], 'forum_users_post_count_idx');
$table->addIndex(['thread_count'], 'forum_users_thread_count_idx');
$table->addIndex(['deleted_at'], 'forum_users_deleted_at_idx');
}
private function createForumUserRolesTable(ISchemaWrapper $schema): void {

View File

@@ -67,18 +67,38 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$table->addUniqueIndex(['user_id', 'thread_id'], 'thread_subs_uniq_idx');
}
/**
* Fix forum_user_stats or forum_users table structure
* Handles both old table name (progressive installs) and new table name (fresh installs)
*/
private function fixForumUserStatsTable(ISchemaWrapper $schema): void {
if (!$schema->hasTable('forum_user_stats')) {
// Determine which table exists (handles both fresh and progressive installs)
$tableName = null;
if ($schema->hasTable('forum_user_stats')) {
$tableName = 'forum_user_stats';
} elseif ($schema->hasTable('forum_users')) {
$tableName = 'forum_users';
}
if ($tableName === null) {
return;
}
$table = $schema->getTable('forum_user_stats');
$table = $schema->getTable($tableName);
// Check if already fixed (has id column)
// Note: On fresh installs, forum_users uses user_id as primary key (no id column needed)
// This fix is only needed for progressive installs with old forum_user_stats structure
if ($table->hasColumn('id')) {
return;
}
// Only add id column to forum_user_stats (old structure)
// forum_users created in Version1 uses user_id as primary key and doesn't need this fix
if ($tableName !== 'forum_user_stats') {
return;
}
// Add id column as auto-increment
$table->addColumn('id', 'bigint', [
'autoincrement' => true,
@@ -123,8 +143,7 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
}
/**
* Rebuild user stats using the old table name (forum_user_stats)
* This is needed because Version8 hasn't renamed the table yet
* Rebuild user stats - handles both old (forum_user_stats) and new (forum_users) table names
*/
private function rebuildAllUserStatsLegacy(): array {
// Get all user IDs from Nextcloud
@@ -133,11 +152,22 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$users[] = $user->getUID();
});
// Determine which table to use
$tableName = $this->getUserStatsTableName();
if ($tableName === null) {
// No table exists yet - this shouldn't happen but handle gracefully
return [
'users' => count($users),
'updated' => 0,
'created' => 0,
];
}
$updated = 0;
$created = 0;
foreach ($users as $userId) {
$wasCreated = $this->rebuildUserStatsLegacy($userId);
$wasCreated = $this->rebuildUserStatsLegacy($userId, $tableName);
if ($wasCreated) {
$created++;
} else {
@@ -153,9 +183,36 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
}
/**
* Rebuild stats for a single user using the old table name
* Get the user stats table name (handles both old and new names)
*/
private function rebuildUserStatsLegacy(string $userId): bool {
private function getUserStatsTableName(): ?string {
// Check forum_users first (new name, for fresh installs)
// Then check forum_user_stats (old name, for progressive installs)
try {
$qb = $this->db->getQueryBuilder();
$qb->select('user_id')->from('forum_users')->setMaxResults(1);
$qb->executeQuery()->closeCursor();
return 'forum_users';
} catch (\Exception $e) {
// Table doesn't exist, try old name
}
try {
$qb = $this->db->getQueryBuilder();
$qb->select('user_id')->from('forum_user_stats')->setMaxResults(1);
$qb->executeQuery()->closeCursor();
return 'forum_user_stats';
} catch (\Exception $e) {
// Neither table exists
}
return null;
}
/**
* Rebuild stats for a single user
*/
private function rebuildUserStatsLegacy(string $userId, string $tableName): bool {
// Count non-deleted threads created by this user
$threadQb = $this->db->getQueryBuilder();
$threadQb->select($threadQb->func()->count('*', 'count'))
@@ -194,10 +251,10 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$lastPostAt = $lastPostResult->fetchOne();
$lastPostResult->closeCursor();
// Check if forum user record already exists (using OLD table name)
// Check if forum user record already exists
$checkQb = $this->db->getQueryBuilder();
$checkQb->select('user_id')
->from('forum_user_stats') // OLD table name!
->from($tableName)
->where($checkQb->expr()->eq('user_id', $checkQb->createNamedParameter($userId)));
$checkResult = $checkQb->executeQuery();
$exists = $checkResult->fetch();
@@ -206,9 +263,9 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$timestamp = time();
if ($exists) {
// Update existing record (using OLD table name)
// Update existing record
$updateQb = $this->db->getQueryBuilder();
$updateQb->update('forum_user_stats') // OLD table name!
$updateQb->update($tableName)
->set('thread_count', $updateQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT))
->set('post_count', $updateQb->createNamedParameter($postCount, IQueryBuilder::PARAM_INT))
->set('updated_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
@@ -221,9 +278,9 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$updateQb->executeStatement();
return false;
} else {
// Create new record (using OLD table name)
// Create new record
$insertQb = $this->db->getQueryBuilder();
$insertQb->insert('forum_user_stats') // OLD table name!
$insertQb->insert($tableName)
->values([
'user_id' => $insertQb->createNamedParameter($userId),
'thread_count' => $insertQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT),
@@ -239,7 +296,7 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
} catch (\Exception $e) {
// If insert fails (race condition), try updating instead
$updateQb = $this->db->getQueryBuilder();
$updateQb->update('forum_user_stats') // OLD table name!
$updateQb->update($tableName)
->set('thread_count', $updateQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT))
->set('post_count', $updateQb->createNamedParameter($postCount, IQueryBuilder::PARAM_INT))
->set('updated_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))

View File

@@ -43,9 +43,18 @@ class Version8Date20251128000000 extends SimpleMigrationStep {
}
}
// Add signature column to user stats
if ($schema->hasTable('forum_user_stats')) {
$table = $schema->getTable('forum_user_stats');
// Add signature column to forum_users table (handles both old and new table names)
// On fresh installs: forum_users is created with signature column in Version1
// On progressive installs: forum_user_stats may still exist and needs signature added
$userTableName = null;
if ($schema->hasTable('forum_users')) {
$userTableName = 'forum_users';
} elseif ($schema->hasTable('forum_user_stats')) {
$userTableName = 'forum_user_stats';
}
if ($userTableName !== null) {
$table = $schema->getTable($userTableName);
if (!$table->hasColumn('signature')) {
$table->addColumn('signature', 'text', [