Skip to content

Doctrine 3 et Postgres : passage des sequences aux identity columns

Auteur : Philippe Le Van - @plv@framapiaf.org

Date : 30 octobre 2025

Contexte

On part d’un projet Symfony avec une base Postgresql.

En changeant de version de symfony (passage en Symfony 6.x à 7.3) et de version de Doctrine (2.x vers 3.4), Doctrine nous suggère de passer de la gestion des id par sequence en gestion des id par identity.

Pour une table "template", ça nous donne la migration suivante :

1
2
$this->addSql('ALTER TABLE template ALTER id TYPE INT');
$this->addSql('ALTER TABLE template ALTER id ADD GENERATED BY DEFAULT AS IDENTITY');

La gestion des id par des identity column, en fait, c’est juste une sequence cachée.

L’ancienne séquence s’appelait template_id_seq et la nouvelle template_id_seq1

Le problème, c’est que la nouvelle séquence repart à 1.

Requêtes utiles

1
2
3
4
5
-- connaitre la valeur d’une séquence :
SELECT last_value FROM template_id_seq1;

-- autre solution. WARNING, ça incrémente la séquence.
select nextval('template_id_seq1')

Mettre à jour la séquence cachée

Solution avec alter table

1
2
3
4
5
select max(id) from template;
-- donne 34

ALTER TABLE template ALTER id RESTART 35;
-- upgrade la séquence

Solution avec setval

On peut faire l’opération en une seule requête en utilisant la fonction setval.

Par contre, ça suppose de connaître le nom de la séquence cachée.

1
SELECT setval('template_id_seq1', (SELECT MAX(id) FROM template), true);

Changer les séquence pour toutes les tables

Sur notre projet Symfony, nous n’avons pas beaucoup de tables. Nous pouvons faire les changements "à la main". Dans notre cas, nous avons fait une boucle en bash avec la liste de toutes les tables.

1
2
3
4
5
for ta in activity checklist checklist_line checklist_template checklist_template_line company document messenger_messages organization template template_file user
do 
  echo "========== TABLE $ta ==========="
  ./bin/console doctrine:query:sql "SELECT setval('${ta}_id_seq1', (SELECT MAX(id) FROM \"${ta}\"), true);"
done

Mais notre table messenger_messages est vide, du coup le SELECT MAX(id)... est vide aussi. On initialise le compteur à 1 pour cette table.

1
./bin/console doctrine:query:sql "SELECT setval('messenger_messages_id_seq1', 1, true);"

Ces modifications ont bien réglé le problème sur notre projet.

Migrations symfony

On n’avait pas anticipé le problème, on a dû le résoudre en prod, mais idéalement, il aurait fallu faire une migration :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20251030072618 extends AbstractMigration
{
    public function getDescription(): string
    {
        return '';
    }

    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $tables = [
            "activity",
            "checklist",
            "checklist_line",
            "checklist_template",
            "checklist_template_line",
            "company",
            "document",
            "organization",
            "template",
            "template_file",
            "user",
        ];
        foreach ($tables as $table) {
            $this->addSql(sprintf(
                "SELECT setval('%s_id_seq1', (SELECT MAX(id) FROM \"%s\"), true);",
                $table,
                $table
            ));
        }
        $this->addSql("SELECT setval('messenger_messages_id_seq1', 1, true);");

    }

    public function down(Schema $schema): void
    {

    }
}