Skip to content

Inconsistent semantics of auto-increment declaration and introspection on SQLite #6844

Closed
@morozov

Description

@morozov
Q A
Version 4.2.x

The meaning of the AUTOINCREMENT attribute in SQLite is different from MySQL. In layman terms,

  1. A column doesn't ned to be declared with AUTOINCREMENT in order to have its value auto-generated and auto-incremented. It's sufficient to declare it as INTEGER PRIMARY KEY.
  2. Declaring it as AUTOINCREMENT only changes the AUTOINCREMENT behavior/implementation (see the difference below).

When DBAL declares an SQLite column, it adds the AUTOINCREMENT keyword only if the column has the autoincrement attribute:

if (! empty($column['autoincrement'])) {
return ' PRIMARY KEY AUTOINCREMENT';

When DBAL introspects an SQLite column, it marks it as autoincrement if it's effectively auto-incremented in the database, not necessarily was declared with the AUTOINCREMENT keyword:

if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
foreach ($list as $column) {
if ($autoincrementColumn !== $column->getName()) {
continue;
}
$column->setAutoincrement(true);

The inconsistency between declaration and introspection can be demonstrated with a test:

public function testPrimaryKeyAutoIncrementComparison(): void
{
    $table = new Table('pk_auto_increment');
    $table->addColumn('id', Types::INTEGER);
    $table->setPrimaryKey(['id']);

    $this->dropAndCreateTable($table);

    $onlineTable = $this->schemaManager->introspectTable('pk_auto_increment');

    self::assertEquals(
        $table->getColumn('id')->getAutoincrement(),
        $onlineTable->getColumn('id')->getAutoincrement(),
    );
}

The test will fail because the id column is declared as autoincrement = false in the application but it's introspected as autoincrement = true.

Difference between INTEGER PRIMARY KEY and INTEGER PRIMARY KEY AUTOINCREMENT

From the documentation:

[...] the purpose of AUTOINCREMENT is to prevent the reuse of ROWIDs from previously deleted rows.

sqlite> -- without AUTOINCREMENT
sqlite> drop table if exists t;
sqlite> create table t (id integer primary key);
sqlite> insert into t values(null);
sqlite> delete from t;
sqlite> insert into t values(null);
sqlite> select * from t;
1 ← a previous value is reused

sqlite> -- with AUTOINCREMENT
sqlite> drop table if exists t;
sqlite> create table t (id integer primary key autoincrement);
sqlite> insert into t values(null);
sqlite> delete from t;
sqlite> insert into t values(null);
sqlite> select * from t;
2 ← the counter keeps increasing

The behavior of INTEGER PRIMARY KEY AUTOINCREMENT is consistent with the rest of supported database platforms and is enforced by SchemaManagerFunctionalTestCase#testPrimaryKeyAutoIncrement():

self::assertGreaterThan($lastUsedIdBeforeDelete, $lastUsedIdAfterDelete);

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions