# SM012: enum_in_transaction

| Property       | Value               |
| -------------- | ------------------- |
| **Rule ID**    | SM012               |
| **Severity**   | ERROR               |
| **Name**       | enum_in_transaction |
| **Applies to** | PostgreSQL only     |

## What Triggers This Rule

This rule triggers when a migration contains `ALTER TYPE ... ADD VALUE` inside a transaction:

```python
# Triggers SM012 (on PostgreSQL)
migrations.RunSQL(
    sql="ALTER TYPE status_enum ADD VALUE 'archived';",
)
```

## Why It's Dangerous

In PostgreSQL, `ALTER TYPE ... ADD VALUE` **cannot run inside a transaction**. This is a PostgreSQL limitation.

When Django runs migrations, it wraps them in a transaction by default. This causes the migration to fail immediately.

### Real-World Failure Scenario

```
Timeline:
├─ 0:00  Migration starts
├─ 0:00  BEGIN TRANSACTION (automatic)
├─ 0:00  ALTER TYPE status_enum ADD VALUE 'archived'
├─ 0:00  ERROR: ALTER TYPE ... ADD cannot run inside a transaction block
└─ 0:00  Migration failed, transaction rolled back
```

## When It's Safe to Ignore

This warning should **never** be ignored when using PostgreSQL. The migration will simply fail if you don't fix it.

## How to Fix It

### Set atomic = False

The fix is simple: disable the transaction wrapper for this migration:

```python
class Migration(migrations.Migration):
    atomic = False  # Required for enum modifications

    operations = [
        migrations.RunSQL(
            sql="ALTER TYPE status_enum ADD VALUE 'archived';",
            reverse_sql=migrations.RunSQL.noop,  # Can't remove enum values
        ),
    ]
```

### Multiple Operations

If your migration has other operations, you may need to split it:

```python
# Migration 1: Add enum value (non-atomic)
class Migration(migrations.Migration):
    atomic = False

    operations = [
        migrations.RunSQL(
            sql="ALTER TYPE status_enum ADD VALUE 'archived';",
            reverse_sql=migrations.RunSQL.noop,
        ),
    ]
```

```python
# Migration 2: Use the new value (atomic is fine)
class Migration(migrations.Migration):
    operations = [
        migrations.AlterField(
            model_name='order',
            name='status',
            field=models.CharField(
                max_length=20,
                choices=[..., ('archived', 'Archived')],
            ),
        ),
    ]
```

### Important: Enum Values Cannot Be Removed

PostgreSQL does not support removing enum values. Once added, they're permanent (unless you recreate the entire type).

```sql
-- This does NOT work:
ALTER TYPE status_enum DROP VALUE 'archived';  -- ERROR!
```

This is why `reverse_sql=migrations.RunSQL.noop` is used.

### Creating a New Enum Type

If you need to "remove" an enum value, you must:

1. Create a new enum type
2. Update the column to use the new type
3. Drop the old type

```python
class Migration(migrations.Migration):
    atomic = False

    operations = [
        migrations.RunSQL(
            sql='''
                -- Create new type without unwanted value
                CREATE TYPE status_enum_new AS ENUM ('pending', 'active', 'completed');

                -- Update column
                ALTER TABLE orders
                ALTER COLUMN status TYPE status_enum_new
                USING status::text::status_enum_new;

                -- Drop old type
                DROP TYPE status_enum;

                -- Rename new type
                ALTER TYPE status_enum_new RENAME TO status_enum;
            ''',
            reverse_sql=migrations.RunSQL.noop,
        ),
    ]
```

## How to Suppress

### In Django Settings

```python
SAFE_MIGRATIONS = {
    "DISABLED_RULES": ["SM012"],
}
```

!!! warning
    Suppressing this rule won't make the migration work. The migration will still fail in PostgreSQL.

## See Also

- [SM007: run_sql_no_reverse](SM007.md) — Related RunSQL rule
- [Safe Patterns: PostgreSQL Enum Values](../patterns.md#postgresql-specific-enum-values)
- [PostgreSQL Documentation: ALTER TYPE](https://www.postgresql.org/docs/current/sql-altertype.html)
