Skip to content

Conversation

@vatsrahul1001
Copy link
Contributor

Fixes critical blocking issues when downgrading Airflow from 3.x to 2.x across PostgreSQL, MySQL, and SQLite databases. These issues were discovered after PR #54399 unblocked the downgrade process.

1. PostgreSQL - NOT NULL Violation in task_reschedule Table

Error:

sqlalchemy.exc.IntegrityError: (psycopg2.errors.NotNullViolation) 
column "try_number" of relation "task_reschedule" contains null values

Root Cause: Migration 0068_3_0_0_ti_table_id_unique_per_try.py:L99 uses default="1" instead of server_default="1", causing existing NULL values to remain NULL when the column is made NOT NULL.

Solution: Replace default="1" with server_default="1" to ensure database-level default value assignment.

2. PostgreSQL - NOT NULL Violation in task_instance_history Table

Error:

sqlalchemy.exc.IntegrityError: (psycopg2.errors.NotNullViolation) 
column "task_instance_id" of relation "task_instance_history" contains null values

Root Cause: The task_instance_id column is made NOT NULL during downgrade but contains NULL values, even though this column gets dropped entirely in the same migration.

Solution: Make task_instance_id column nullable since it's immediately dropped and not needed in 2.x schema.

3. MySQL - Invalid NULL Value During Row Numbering

Error:

sqlalchemy.exc.OperationalError: (MySQLdb.OperationalError) (1138, 'Invalid use of NULL value')

Root Cause: MySQL query attempts to JOIN on NULL id values in task_instance_history:

UPDATE task_instance_history tih
JOIN (
    SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS row_num
    FROM task_instance_history
) AS temp ON tih.id = temp.id
SET tih.id = temp.row_num;

The id column was just re-added as nullable with all NULL values, making the JOIN fail.

Solution: Replace with MySQL variable-based sequential numbering:

SET @row_number = 0;
UPDATE task_instance_history 
SET id = (@row_number := @row_number + 1)
ORDER BY try_id;

4. SQLite - Foreign Key Constraint During Batch Operations

Error:

sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) FOREIGN KEY constraint failed

Root Cause: SQLite's strict foreign key checking prevents dropping the dag table during batch_alter_table operations when other tables reference it.

Solution: Add SQLite-specific handling to temporarily disable foreign key constraints:

if dialect_name == "sqlite":
    conn.execute(text("PRAGMA foreign_keys=OFF"))
    try:
        # batch operations
    finally:
        conn.execute(text("PRAGMA foreign_keys=ON"))

Testing

Verified successful downgrade from Airflow 3.0.5rc1 to 2.11 on:

  • PostgreSQL
  • MySQL
  • SQLite

@vatsrahul1001 vatsrahul1001 changed the title fix migrations Fix downgrade migrations failures Aug 14, 2025
@kaxil kaxil added this to the Airflow 3.0.5 milestone Aug 14, 2025
@vatsrahul1001 vatsrahul1001 requested a review from ashb August 14, 2025 14:13
@vatsrahul1001
Copy link
Contributor Author

@ashb, test failure, looks unrelated

@ashb
Copy link
Member

ashb commented Aug 14, 2025

@vatsrahul1001 It does look unrelated, but it's persistent across retries so I'm loathe to merge this unless something else is also exhibiting this failure

@ashb
Copy link
Member

ashb commented Aug 14, 2025

Ah main build too -- okay we can merge.

@kaxil kaxil added the backport-to-v3-1-test Mark PR with this label to backport to v3-1-test branch label Aug 14, 2025
@kaxil kaxil merged commit bc18493 into apache:main Aug 14, 2025
145 of 150 checks passed
@kaxil kaxil deleted the fix-downgrade-issues branch August 14, 2025 17:44
@github-actions
Copy link

Backport failed to create: v3-0-test. View the failure log Run details

Status Branch Result
v3-0-test Commit Link

You can attempt to backport this manually by running:

cherry_picker bc18493 v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil pushed a commit that referenced this pull request Aug 14, 2025
Fixes critical blocking issues when downgrading Airflow from 3.x to 2.x across PostgreSQL, MySQL, and SQLite databases. These issues were discovered after [PR #54399](#54399) unblocked the downgrade process.

**Error:**
```
sqlalchemy.exc.IntegrityError: (psycopg2.errors.NotNullViolation)
column "try_number" of relation "task_reschedule" contains null values
```

**Root Cause:** Migration [`0068_3_0_0_ti_table_id_unique_per_try.py:L99`](https://github.com/apache/airflow/blob/main/airflow-core/src/airflow/migrations/versions/0068_3_0_0_ti_table_id_unique_per_try.py#L99) uses `default="1"` instead of `server_default="1"`, causing existing NULL values to remain NULL when the column is made NOT NULL.

**Solution:** Replace `default="1"` with `server_default="1"` to ensure database-level default value assignment.

**Error:**
```
sqlalchemy.exc.IntegrityError: (psycopg2.errors.NotNullViolation)
column "task_instance_id" of relation "task_instance_history" contains null values
```

**Root Cause:** The `task_instance_id` column is made NOT NULL during downgrade but contains NULL values, even though this column gets dropped entirely in the same migration.

**Solution:** Make `task_instance_id` column nullable since it's immediately dropped and not needed in 2.x schema.

**Error:**
```
sqlalchemy.exc.OperationalError: (MySQLdb.OperationalError) (1138, 'Invalid use of NULL value')
```

**Root Cause:** MySQL query attempts to JOIN on NULL `id` values in `task_instance_history`:
```sql
UPDATE task_instance_history tih
JOIN (
    SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS row_num
    FROM task_instance_history
) AS temp ON tih.id = temp.id
SET tih.id = temp.row_num;
```
The `id` column was just re-added as nullable with all NULL values, making the JOIN fail.

**Solution:** Replace with MySQL variable-based sequential numbering:
```sql
SET @row_number = 0;
UPDATE task_instance_history
SET id = (@row_number := @row_number + 1)
ORDER BY try_id;
```

**Error:**
```
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) FOREIGN KEY constraint failed
```

**Root Cause:** SQLite's strict foreign key checking prevents dropping the `dag` table during `batch_alter_table` operations when other tables reference it.

**Solution:** Add SQLite-specific handling to temporarily disable foreign key constraints:
```python
if dialect_name == "sqlite":
    conn.execute(text("PRAGMA foreign_keys=OFF"))
    try:
        # batch operations
    finally:
        conn.execute(text("PRAGMA foreign_keys=ON"))
```
Verified successful downgrade from Airflow 3.0.5rc1 to 2.11 on:
- [x] PostgreSQL
- [x] MySQL
- [x] SQLite

(cherry picked from commit bc18493)
@MASTREX
Copy link

MASTREX commented Sep 13, 2025

Solution: Make task_instance_id column nullable since it's immediately dropped and not needed in 2.x schema.

Hey @vatsrahul1001 Can you explain this in more detail. I did not get why this column is not needed in 2.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:db-migrations PRs with DB migration backport-to-v3-1-test Mark PR with this label to backport to v3-1-test branch kind:documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants