From 0fa910a44ba3cc71ab07bf695807561dc757cf3b Mon Sep 17 00:00:00 2001 From: albertlast Date: Sun, 2 Aug 2026 21:28:41 +0200 Subject: [PATCH] Syncs PostgreSQL sequences with the rows the installer inserts A table's initial data carries its own IDs - the default board is board 1, and other rows in it refer to each other by number - so those values are supplied to the INSERT rather than generated. MySQL sees a value larger than its AUTO_INCREMENT counter and moves the counter along by itself. PostgreSQL does not: a sequence only advances when something calls nextval() on it, nothing has, so it still sits at 1 and hands 1 to the next insert. That collides with the row already there and the insert fails on the primary key. On a new PostgreSQL forum, 28 sequences are in that state. Starting the first topic is the usual way to meet it, and the symptom is not obviously a database problem: Post2 runs on to its normal redirect, the browser lands on the board, and no topic is there. It then works on the second try, because the attempt that failed still consumed the 1 and the retry gets 2 - which makes it look like a fluke rather than something to report. The 2.1 upgrade path has carried this fix for years, as the PostgreSqlSequences migration. This is the same setval() for a fresh install, next to the insert that causes it, and it is a no-op on MySQL. Not covered by the unit suite: it needs a database, and it only shows up on a forum that has just been installed. Verified by installing on PostgreSQL and posting - the first topic now works where it previously vanished - and by checking that the sequences report a position instead of never having been called. MySQL was reinstalled and exercised too, since populate() is shared. Co-Authored-By: Claude Opus 5 Signed-off-by: albertlast --- Sources/Db/Schema/Table.php | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index c91e3e83225..73e2e75bfb0 100644 --- a/Sources/Db/Schema/Table.php +++ b/Sources/Db/Schema/Table.php @@ -592,6 +592,10 @@ public function populate(bool $replace = false): int $num_inserts = \count($ids ?? $this->initial_data); + if (isset($auto_col)) { + $this->resyncAutoIncrement($auto_col); + } + return $num_inserts; } @@ -697,4 +701,46 @@ final public static function getInitializers(string $schema_version): array return []; } + + /****************** + * Internal methods + ******************/ + + /** + * Points the table's ID generator past the rows that were just inserted. + * + * The initial data carries its own IDs — the default board is board 1, and + * plenty of other rows are referred to by number elsewhere in it — so they + * are supplied rather than generated. + * + * MySQL notices that and moves AUTO_INCREMENT along by itself. PostgreSQL + * does not: a sequence only advances when something calls nextval() on it, + * and nothing has, so it still sits at 1 and hands 1 to the next insert. + * That collides with the row already there, and the insert fails on the + * primary key. The first topic anybody starts on a new forum is the usual + * way to meet this, and it fails once and then works, because the failed + * attempt consumed the 1 and the retry gets 2. + * + * The 2.1 upgrade path has had this fix for years, in the PostgreSqlSequences + * migration. This is the same thing for a fresh install. + * + * @param string $auto_col Name of the auto-incrementing column. + */ + private function resyncAutoIncrement(string $auto_col): void + { + if (Db::$db->title !== POSTGRE_TITLE) { + return; + } + + // COALESCE for the table that ended up empty after an 'ignore' insert: + // setval() will not accept NULL, and 1 is where the sequence began. + Db::$db->query( + 'SELECT setval(\'{raw:sequence}\', COALESCE((SELECT MAX({raw:column}) FROM {db_prefix}{raw:table}), 1))', + [ + 'sequence' => Db::$db->prefix . $this->name . '_seq', + 'column' => $auto_col, + 'table' => $this->name, + ], + ); + } }