# SM013: alter_varchar_length

| Property       | Value                |
| -------------- | -------------------- |
| **Rule ID**    | SM013                |
| **Severity**   | WARNING              |
| **Name**       | alter_varchar_length |
| **Applies to** | All databases        |

## What Triggers This Rule

This rule triggers when a migration decreases the length of a VARCHAR/CharField:

```python
# Triggers SM013
migrations.AlterField(
    model_name='user',
    name='username',
    field=models.CharField(max_length=50),  # Was max_length=100
)
```

## Why It's Dangerous

Decreasing VARCHAR length can cause:

1. **Data truncation** — Existing data longer than new limit is lost
2. **Migration failure** — Database may reject the change if data exceeds new limit
3. **Table rewrite** — Some databases rewrite the entire table

### PostgreSQL Behavior

PostgreSQL will **fail** if any existing value exceeds the new length:

```
ERROR: value too long for type character varying(50)
```

### MySQL Behavior

MySQL may **silently truncate** data (depending on SQL mode):

```sql
-- With strict mode: ERROR
-- Without strict mode: Data truncated silently (data loss!)
```

### Real-World Failure Scenario

```
Timeline:
├─ 0:00  Migration: ALTER TABLE users ALTER COLUMN username TYPE VARCHAR(50)
├─ 0:01  Checking existing data...
├─ 0:01  ERROR: value too long for type character varying(50)
├─ 0:01  Detail: Current value "alexandra.constantinescu@example.com" is 42 chars
└─ 0:01  Migration failed
```

Or worse with MySQL without strict mode:

```
Timeline:
├─ 0:00  Migration runs
├─ 0:01  Data silently truncated
├─ 0:02  User "alexandra.constantinescu" is now "alexandra.constanti"
└─ ...   Data corruption discovered later
```

## When It's Safe to Ignore

This warning can be safely ignored when:

1. **Data is verified to fit** — All existing values are within new limit
2. **Intentional truncation** — You want to enforce the shorter limit
3. **Column is empty** — No data to truncate

## How to Fix It

### Step 1: Check Existing Data

Before changing the length, verify all data fits:

```sql
-- Find values that would be truncated
SELECT id, username, LENGTH(username) as len
FROM users
WHERE LENGTH(username) > 50
ORDER BY len DESC;
```

### Step 2: Clean Data (If Needed)

If data exists that exceeds the new limit:

```python
def clean_usernames(apps, schema_editor):
    User = apps.get_model('myapp', 'User')
    for user in User.objects.filter(username__regex=r'^.{51,}$'):
        # Option 1: Truncate
        user.username = user.username[:50]
        # Option 2: Generate new value
        # user.username = f"user_{user.id}"
        user.save()

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

### Step 3: Apply the Change

Only after data is cleaned:

```python
migrations.AlterField(
    model_name='user',
    name='username',
    field=models.CharField(max_length=50),
)
```

### Alternative: Keep Larger Size

If the data legitimately needs the larger size, don't reduce it. Database storage is cheap; data loss is expensive.

## Safe VARCHAR Changes

| Change                          | Safety                    |
| ------------------------------- | ------------------------- |
| `VARCHAR(100)` → `VARCHAR(200)` | ✅ Safe (increasing)      |
| `VARCHAR(100)` → `TEXT`         | ✅ Safe (no limit)        |
| `VARCHAR(100)` → `VARCHAR(50)`  | ⚠️ Dangerous (decreasing) |

## How to Suppress

### In Django Settings

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

## See Also

- [SM004: alter_column_type](SM004.md) — General column type changes
- [Safe Patterns: Changing Column Types](../patterns.md#changing-column-types)
