|
22 | 22 | IntModelWithExplicitPK, |
23 | 23 | ModelWithCheckConstraint, |
24 | 24 | ModelWithForeignKey, |
| 25 | + ModelWithNotNullForeignKey, |
25 | 26 | NotNullIntFieldModel, |
26 | 27 | NullFKFieldModel, |
27 | 28 | NullIntFieldModel, |
@@ -2586,6 +2587,135 @@ def test_operation(self): |
2586 | 2587 | AND convalidated IS TRUE; |
2587 | 2588 | """) |
2588 | 2589 |
|
| 2590 | + @pytest.mark.django_db(transaction=True) |
| 2591 | + def test_when_column_not_null(self): |
| 2592 | + with connection.cursor() as cursor: |
| 2593 | + # Set the lock_timeout to check it has been returned to |
| 2594 | + # its original value once the fk index creation is completed by |
| 2595 | + # the reverse operation. |
| 2596 | + cursor.execute(_SET_LOCK_TIMEOUT) |
| 2597 | + |
| 2598 | + project_state = ProjectState() |
| 2599 | + project_state.add_model(ModelState.from_model(IntModel)) |
| 2600 | + project_state.add_model(ModelState.from_model(ModelWithNotNullForeignKey)) |
| 2601 | + new_state = project_state.clone() |
| 2602 | + operation = operations.SaferRemoveFieldForeignKey( |
| 2603 | + model_name="modelwithnotnullforeignkey", |
| 2604 | + name="fk", |
| 2605 | + ) |
| 2606 | + |
| 2607 | + assert operation.describe() == ( |
| 2608 | + "Remove field fk from modelwithnotnullforeignkey. Note: Using " |
| 2609 | + "django_pg_migration_tools SaferRemoveFieldForeignKey operation." |
| 2610 | + ) |
| 2611 | + |
| 2612 | + operation.state_forwards(self.app_label, new_state) |
| 2613 | + with connection.schema_editor(atomic=False, collect_sql=False) as editor: |
| 2614 | + with utils.CaptureQueriesContext(connection) as queries: |
| 2615 | + operation.database_forwards( |
| 2616 | + self.app_label, editor, from_state=project_state, to_state=new_state |
| 2617 | + ) |
| 2618 | + |
| 2619 | + assert len(queries) == 2 |
| 2620 | + |
| 2621 | + assert queries[0]["sql"] == dedent(""" |
| 2622 | + SELECT 1 |
| 2623 | + FROM pg_catalog.pg_attribute |
| 2624 | + WHERE |
| 2625 | + attrelid = 'example_app_modelwithnotnullforeignkey'::regclass |
| 2626 | + AND attname = 'fk_id'; |
| 2627 | + """) |
| 2628 | + assert queries[1]["sql"] == dedent(""" |
| 2629 | + ALTER TABLE "example_app_modelwithnotnullforeignkey" |
| 2630 | + DROP COLUMN "fk_id"; |
| 2631 | + """) |
| 2632 | + |
| 2633 | + with connection.schema_editor(atomic=False, collect_sql=False) as editor: |
| 2634 | + with utils.CaptureQueriesContext(connection) as reverse_queries: |
| 2635 | + operation.database_backwards( |
| 2636 | + self.app_label, editor, from_state=new_state, to_state=project_state |
| 2637 | + ) |
| 2638 | + |
| 2639 | + assert len(reverse_queries) == 9 |
| 2640 | + |
| 2641 | + assert reverse_queries[0]["sql"] == dedent(""" |
| 2642 | + SELECT 1 |
| 2643 | + FROM pg_catalog.pg_attribute |
| 2644 | + WHERE |
| 2645 | + attrelid = 'example_app_modelwithnotnullforeignkey'::regclass |
| 2646 | + AND attname = 'fk_id'; |
| 2647 | + """) |
| 2648 | + assert reverse_queries[1]["sql"] == dedent(""" |
| 2649 | + ALTER TABLE "example_app_modelwithnotnullforeignkey" |
| 2650 | + ADD COLUMN IF NOT EXISTS "fk_id" |
| 2651 | + integer NULL; |
| 2652 | + """) |
| 2653 | + assert reverse_queries[2]["sql"] == "SHOW lock_timeout;" |
| 2654 | + assert reverse_queries[3]["sql"] == "SET lock_timeout = '0';" |
| 2655 | + assert reverse_queries[4]["sql"] == dedent(""" |
| 2656 | + SELECT relname |
| 2657 | + FROM pg_class, pg_index |
| 2658 | + WHERE ( |
| 2659 | + pg_index.indisvalid = false |
| 2660 | + AND pg_index.indexrelid = pg_class.oid |
| 2661 | + AND relname = 'modelwithnotnullforeignkey_fk_id_idx' |
| 2662 | + ); |
| 2663 | + """) |
| 2664 | + assert ( |
| 2665 | + reverse_queries[5]["sql"] |
| 2666 | + == 'CREATE INDEX CONCURRENTLY IF NOT EXISTS "modelwithnotnullforeignkey_fk_id_idx" ON "example_app_modelwithnotnullforeignkey" ("fk_id");' |
| 2667 | + ) |
| 2668 | + assert reverse_queries[6]["sql"] == "SET lock_timeout = '1s';" |
| 2669 | + assert reverse_queries[7]["sql"] == dedent(""" |
| 2670 | + ALTER TABLE "example_app_modelwithnotnullforeignkey" |
| 2671 | + ADD CONSTRAINT "example_app_modelwithnotnullforeignkey_fk_id_fk" FOREIGN KEY ("fk_id") |
| 2672 | + REFERENCES "example_app_intmodel" ("id") |
| 2673 | + DEFERRABLE INITIALLY DEFERRED |
| 2674 | + NOT VALID; |
| 2675 | + """) |
| 2676 | + assert reverse_queries[8]["sql"] == dedent(""" |
| 2677 | + ALTER TABLE "example_app_modelwithnotnullforeignkey" |
| 2678 | + VALIDATE CONSTRAINT "example_app_modelwithnotnullforeignkey_fk_id_fk"; |
| 2679 | + """) |
| 2680 | + |
| 2681 | + # Reversing again does nothing apart from checking that the FK is |
| 2682 | + # already there and the index/constraint are all good to go. |
| 2683 | + # This proves the OP is idempotent. |
| 2684 | + with connection.schema_editor(atomic=False, collect_sql=False) as editor: |
| 2685 | + with utils.CaptureQueriesContext(connection) as second_reverse_queries: |
| 2686 | + operation.database_backwards( |
| 2687 | + self.app_label, editor, from_state=new_state, to_state=project_state |
| 2688 | + ) |
| 2689 | + assert len(second_reverse_queries) == 4 |
| 2690 | + assert second_reverse_queries[0]["sql"] == dedent(""" |
| 2691 | + SELECT 1 |
| 2692 | + FROM pg_catalog.pg_attribute |
| 2693 | + WHERE |
| 2694 | + attrelid = 'example_app_modelwithnotnullforeignkey'::regclass |
| 2695 | + AND attname = 'fk_id'; |
| 2696 | + """) |
| 2697 | + assert second_reverse_queries[1]["sql"] == dedent(""" |
| 2698 | + SELECT 1 |
| 2699 | + FROM pg_class, pg_index |
| 2700 | + WHERE ( |
| 2701 | + pg_index.indisvalid = true |
| 2702 | + AND pg_index.indexrelid = pg_class.oid |
| 2703 | + AND relname = 'modelwithnotnullforeignkey_fk_id_idx' |
| 2704 | + ); |
| 2705 | + """) |
| 2706 | + assert second_reverse_queries[2]["sql"] == dedent(""" |
| 2707 | + SELECT conname |
| 2708 | + FROM pg_catalog.pg_constraint |
| 2709 | + WHERE conname = 'example_app_modelwithnotnullforeignkey_fk_id_fk'; |
| 2710 | + """) |
| 2711 | + assert second_reverse_queries[3]["sql"] == dedent(""" |
| 2712 | + SELECT 1 |
| 2713 | + FROM pg_catalog.pg_constraint |
| 2714 | + WHERE |
| 2715 | + conname = 'example_app_modelwithnotnullforeignkey_fk_id_fk' |
| 2716 | + AND convalidated IS TRUE; |
| 2717 | + """) |
| 2718 | + |
2589 | 2719 | @pytest.mark.django_db(transaction=True) |
2590 | 2720 | def test_when_column_already_deleted(self): |
2591 | 2721 | with connection.cursor() as cursor: |
|
0 commit comments