Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/en/annotated/entity.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,62 @@ class User
**Use cases:**

- Auto-increment primary keys (`primary`, `bigPrimary`)
- Auto-increment columns outside the primary key (see below)
- Database-side DEFAULT expressions
- Database triggers that populate columns

**Auto-increment columns outside the primary key**

On PostgreSQL a `serial`, `bigserial` or `smallserial` column gets its value from a sequence and is not part of the
primary key:

```php
#[Entity]
class Order
{
#[Column(type: 'primary')]
private ?int $id = null;

// PostgreSQL only: "number" serial NOT NULL
#[Column(type: 'serial')]
#[GeneratedValue(onInsert: true)]
private ?int $number = null;

#[Column(type: 'string')]
private string $title;
}
```

After `persist()` both `$id` and `$number` hold the values PostgreSQL generated: the ORM reads every `onInsert`
field back with `INSERT ... RETURNING`. Fields of the `serial`, `bigserial` and `smallserial` types are treated as
`onInsert` even without the attribute.

Other drivers:

- **MySQL** (since cycle/database 2.23.4): use an integer column with `autoIncrement: true` and an index on it:

```php
#[Entity]
#[Index(columns: ['number'], unique: true)]
class Order
{
#[Column(type: 'string(36)', primary: true)]
private string $id;

#[Column(type: 'integer', autoIncrement: true)]
#[GeneratedValue(onInsert: true)]
private ?int $number = null;
}
```

MySQL has no `RETURNING`: the ORM reads the last insert ID only into a single primary key that was not set, so
`$number` stays `null` after `persist()` until the entity is loaded again.
- **SQLite, SQL Server**: not supported. There `serial` is passed to the database as a native type name. On SQLite this
makes a plain `NOT NULL` column, and the `INSERT` fails.

See [Auto-increment Columns Outside the Primary Key](/docs/en/database/declaration.md#auto-increment-columns-outside-the-primary-key)
for the schema side.

#### 2. PHP-Generated Before Insert (`beforeInsert: true`)

The ORM generates the value in PHP before sending the INSERT query to the database.
Expand Down
74 changes: 74 additions & 0 deletions docs/en/database/declaration.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ $schema->string('username', 64);
- Only one auto-increment column per table
- Automatically added to table's primary key index
- Use `bigPrimary` for tables expecting billions of records
- For an auto-incremented column that is not the primary key, see
[Auto-increment Columns Outside the Primary Key](#auto-increment-columns-outside-the-primary-key)

#### Integer Types

Expand Down Expand Up @@ -495,6 +497,78 @@ $schema->save();
> **Important**
> Primary keys can only be set during table creation. You cannot change them after the table exists.

### Auto-increment Columns Outside the Primary Key

Sometimes a counter column is needed next to the primary key, such as a human-readable order number in a table keyed
by UUID. Support depends on the driver:

| Driver | Declaration | Result |
|------------|---------------------------------------------------------|-------------------------------------------------------------------------|
| PostgreSQL | `serial()`, `bigSerial()`, `smallSerial()` | A column with its own sequence, `NOT NULL`, not part of the primary key |
| MySQL | `integer()` / `bigInteger()` with `autoIncrement: true` | `AUTO_INCREMENT` column with its own index, since cycle/database 2.23.4 |
| SQLite | not supported | `serial()` throws `SchemaException`, `autoIncrement: true` is ignored |
| SQL Server | not supported | `serial()` throws `SchemaException`, `autoIncrement: true` is ignored |

#### PostgreSQL: serial columns

`serial()`, `bigSerial()` and `smallSerial()` (available since cycle/database 2.5.0) create `serial`, `bigserial` and
`smallserial` columns. Unlike `primary()`, `bigPrimary()` and `smallPrimary()`, they are not added to the primary key,
so a table can have several of them, or no primary key at all:

```php
$schema = $database->table('orders')->getSchema();

$schema->uuid('id')->nullable(false);
$schema->serial('number'); // "number" serial NOT NULL
$schema->string('title');

$schema->setPrimaryKeys(['id']);
$schema->save();
```

PostgreSQL fills `number` from the column's sequence when an `INSERT` does not set it.

#### MySQL: autoIncrement attribute

Since cycle/database 2.23.4 an integer column with the `autoIncrement` attribute can stay outside the primary key.
MySQL requires an `AUTO_INCREMENT` column to be indexed, so give it a unique or a plain index: Cycle writes that index
into the `CREATE TABLE` statement.

```php
$schema = $database->table('orders')->getSchema();

$schema->string('id', 36)->nullable(false);
$schema->integer('number', autoIncrement: true)->nullable(false); // `number` int(11) NOT NULL AUTO_INCREMENT
$schema->string('title');

$schema->setPrimaryKeys(['id']); // PRIMARY KEY (id)
$schema->index(['number'])->unique();

$schema->save();
```

`bigInteger()` works the same way. Without an index on the column, `save()` throws `SchemaException`:
``AUTO_INCREMENT column `number` of table `orders` must be in the primary key or in an index``.

Limitations:

- A table has only one `AUTO_INCREMENT` column, so such a counter cannot be combined with `primary()`.
- An existing table cannot get the counter yet: adding an `AUTO_INCREMENT` column to it fails with MySQL error 1075,
because the column and its index are added by separate statements. Only a new table can have it.
- Before cycle/database 2.23.4 the column was always added to the primary key, and the table could be created only with
the counter first in it: `setPrimaryKeys(['number', 'id'])`.

#### SQLite and SQL Server

Neither driver has a schema option for an auto-incremented column outside the primary key: `serial()`, `bigSerial()`
and `smallSerial()` throw `SchemaException: Undefined abstract/virtual type`, while `autoIncrement` and `identity`
arguments are silently ignored and produce a plain integer column. Fill the value in the application, or keep such
code to PostgreSQL and MySQL.

> **Note**
> To read the generated value back into an entity, mark the field with `#[GeneratedValue(onInsert: true)]`. See
> [Generated Values](/docs/en/annotated/entity.md#generated-values).

## Indexes

### Simple Indexes
Expand Down