# SM004: alter_column_type

| Property       | Value             |
| -------------- | ----------------- |
| **Rule ID**    | SM004             |
| **Severity**   | WARNING           |
| **Name**       | alter_column_type |
| **Applies to** | All databases     |

## What Triggers This Rule

This rule triggers when a migration contains `AlterField` that changes the column type:

```python
# Triggers SM004
migrations.AlterField(
    model_name='user',
    name='age',
    field=models.CharField(max_length=10),  # Was IntegerField
)
```

## Why It's Dangerous

Changing a column's data type often requires the database to:

1. **Lock the table** for the duration of the operation
2. **Rewrite every row** with the new column format
3. **Validate data** can be converted to the new type

### High-Risk Changes

| From            | To             | Risk                           |
| --------------- | -------------- | ------------------------------ |
| `INTEGER`       | `VARCHAR`      | Table rewrite                  |
| `VARCHAR`       | `INTEGER`      | Validation + rewrite, may fail |
| `VARCHAR(200)`  | `VARCHAR(100)` | May truncate data              |
| `TEXT`          | `VARCHAR(255)` | May truncate, table rewrite    |
| `DECIMAL(10,2)` | `INTEGER`      | Data loss (decimals)           |

### Lower-Risk Changes

| From           | To             | Risk                      |
| -------------- | -------------- | ------------------------- |
| `VARCHAR(100)` | `VARCHAR(200)` | Usually safe (no rewrite) |
| `VARCHAR`      | `TEXT`         | Safe on PostgreSQL        |
| `INTEGER`      | `BIGINT`       | Usually safe              |

### Real-World Failure Scenario

```
Timeline (changing VARCHAR(100) to VARCHAR(50)):
├─ 0:00  Migration starts
├─ 0:00  Table locked
├─ 0:01  ERROR: value too long for type character varying(50)
├─ 0:01  Migration failed, rolled back
└─ 0:01  Deployment failed
```

Or on a large table:

```
Timeline (changing INTEGER to VARCHAR):
├─ 0:00   Migration starts, table locked
├─ 0:05   Rewriting 10M rows...
├─ 0:10   Still rewriting...
├─ 0:15   All queries timing out
├─ 0:20   Finally complete
└─ 0:20   Application recovers
```

## When It's Safe to Ignore

This warning can be safely ignored when:

1. **Increasing VARCHAR length** — Usually a metadata-only change
2. **Widening numeric types** — `INTEGER` → `BIGINT`
3. **The table is small** — Less than a few thousand rows
4. **Using maintenance mode** — No traffic during migration

## How to Fix It

### For Incompatible Type Changes

Use the add-copy-remove pattern:

```python
# Migration 1: Add new column
migrations.AddField(
    model_name='user',
    name='age_text',
    field=models.CharField(max_length=10, null=True),
)
```

```python
# Migration 2: Copy and convert data
def convert_age(apps, schema_editor):
    User = apps.get_model('myapp', 'User')
    for user in User.objects.all().iterator():
        user.age_text = str(user.age)
        user.save(update_fields=['age_text'])

migrations.RunPython(convert_age, migrations.RunPython.noop)
```

```python
# Migration 3: Update code to use new column, deploy

# Migration 4: Remove old column
migrations.RemoveField(
    model_name='user',
    name='age',
)

# Optionally rename new column
migrations.RenameField(
    model_name='user',
    old_name='age_text',
    new_name='age',
)
```

### For Reducing VARCHAR Length

First verify no data will be truncated:

```sql
SELECT COUNT(*) FROM users WHERE LENGTH(field_name) > 50;
```

If data exists, either:

1. Clean up the data first
2. Keep the larger size
3. Migrate data to a new column

## How to Suppress

### In Django Settings

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

## See Also

- [SM001: not_null_without_default](SM001.md) — Related field modification rule
- [SM013: alter_varchar_length](SM013.md) — Specific to VARCHAR length changes
- [Safe Patterns: Changing Column Types](../patterns.md#changing-column-types)
