# SM002: drop_column_unsafe

| Property       | Value              |
| -------------- | ------------------ |
| **Rule ID**    | SM002              |
| **Severity**   | WARNING            |
| **Name**       | drop_column_unsafe |
| **Applies to** | All databases      |

## What Triggers This Rule

This rule triggers when a migration contains `RemoveField`:

```python
# Triggers SM002
migrations.RemoveField(
    model_name='user',
    name='legacy_field',
)
```

## Why It's Dangerous

During a rolling deployment, you have multiple application instances running:

1. **Old instances** — Still running the previous code version
2. **New instances** — Running the updated code

If you drop a column while old code is still running:

```
Timeline:
├─ 0:00  Migration runs → column deleted
├─ 0:01  Server 1 (old code) receives request
├─ 0:01  Old code: SELECT id, legacy_field FROM users
├─ 0:01  Database: ERROR - column "legacy_field" does not exist
├─ 0:01  User sees 500 error
├─ ...
├─ 0:05  Server 1 finally gets new code
└─ 0:05  All servers healthy
```

### Real-World Impact

- **500 errors** for users during the deployment window
- **Failed background jobs** that reference the column
- **Broken admin pages** if the field was displayed there
- **Cache invalidation issues** if cached objects include the field

## When It's Safe to Ignore

This warning can be safely ignored when:

1. **The column is truly unused** — No code, queries, or ORM references exist
2. **You're using blue-green deployment** — Complete cutover, no rolling update
3. **Maintenance mode is enabled** — No traffic during deployment
4. **All code references were removed in a previous deployment**

## How to Fix It

### Recommended: Two-Phase Removal

**Phase 1: Remove Code References**

1. Remove the field from your model (but keep `db_column` to prevent migration)
2. Remove all code that references the field
3. Deploy this change and wait for all instances to update

```python
# Model change - keep data but hide from Django
class User(models.Model):
    # legacy_field = models.CharField(...)  # Removed from model
    pass
```

**Phase 2: Remove the Column**

After all servers have the new code:

```python
# Now safe to remove
migrations.RemoveField(
    model_name='user',
    name='legacy_field',
)
```

### Alternative: Make Nullable First

If you want extra safety:

```python
# Migration 1: Make nullable (handles any stray references gracefully)
migrations.AlterField(
    model_name='user',
    name='legacy_field',
    field=models.CharField(max_length=100, null=True, blank=True),
)
```

```python
# Migration 2: After full deployment, remove
migrations.RemoveField(
    model_name='user',
    name='legacy_field',
)
```

## How to Suppress

### In Django Settings

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

### Suppress for Specific Models (Future Feature)

```python
SAFE_MIGRATIONS = {
    "RULE_CONFIG": {
        "SM002": {
            "ignore_models": ["LegacyModel", "DeprecatedTable"],
        },
    },
}
```

## See Also

- [SM003: drop_table_unsafe](SM003.md) — Similar rule for dropping tables
- [SM006: rename_column](SM006.md) — Related rule for renaming columns
- [Safe Patterns: Removing Columns](../patterns.md#removing-columns)
