Fix postgres fk rename (#378)

* Update postgres drop fk template

* fix test error

* docs: update changlog
This commit is contained in:
Waket Zheng 2024-12-09 11:41:40 +08:00 committed by GitHub
parent 8cefe68c9b
commit c2ebe9b5e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 39 additions and 49 deletions

View File

@ -5,6 +5,7 @@
### [0.8.1](Unreleased) ### [0.8.1](Unreleased)
#### Fixed #### Fixed
- PostgreSQL: Cannot drop constraint after deleting or rename FK on a model. (#378)
- Sort m2m fields before comparing them with diff. (#271) - Sort m2m fields before comparing them with diff. (#271)
### [0.8.0](../../releases/tag/v0.8.0) - 2024-12-04 ### [0.8.0](../../releases/tag/v0.8.0) - 2024-12-04

View File

@ -16,7 +16,7 @@ class PostgresDDL(BaseDDL):
'ALTER TABLE "{table_name}" ALTER COLUMN "{column}" TYPE {datatype}{using}' 'ALTER TABLE "{table_name}" ALTER COLUMN "{column}" TYPE {datatype}{using}'
) )
_SET_COMMENT_TEMPLATE = 'COMMENT ON COLUMN "{table_name}"."{column}" IS {comment}' _SET_COMMENT_TEMPLATE = 'COMMENT ON COLUMN "{table_name}"."{column}" IS {comment}'
_DROP_FK_TEMPLATE = 'ALTER TABLE "{table_name}" DROP CONSTRAINT "{fk_name}"' _DROP_FK_TEMPLATE = 'ALTER TABLE "{table_name}" DROP CONSTRAINT IF EXISTS "{fk_name}"'
def alter_column_null(self, model: "Type[Model]", field_describe: dict) -> str: def alter_column_null(self, model: "Type[Model]", field_describe: dict) -> str:
db_table = model._meta.db_table db_table = model._meta.db_table

View File

@ -49,7 +49,7 @@ def default_name():
class Category(Model): class Category(Model):
slug = fields.CharField(max_length=100) slug = fields.CharField(max_length=100)
name = fields.CharField(max_length=200, null=True, default=default_name) name = fields.CharField(max_length=200, null=True, default=default_name)
user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( owner: fields.ForeignKeyRelation[User] = fields.ForeignKeyField(
"models.User", description="User" "models.User", description="User"
) )
title = fields.CharField(max_length=20, unique=False) title = fields.CharField(max_length=20, unique=False)

View File

@ -16,8 +16,8 @@ def test_create_table():
`name` VARCHAR(200), `name` VARCHAR(200),
`title` VARCHAR(20) NOT NULL, `title` VARCHAR(20) NOT NULL,
`created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`user_id` INT NOT NULL COMMENT 'User', `owner_id` INT NOT NULL COMMENT 'User',
CONSTRAINT `fk_category_user_e2e3874c` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE CONSTRAINT `fk_category_user_110d4c63` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
) CHARACTER SET utf8mb4""" ) CHARACTER SET utf8mb4"""
) )
@ -30,7 +30,7 @@ def test_create_table():
"name" VARCHAR(200), "name" VARCHAR(200),
"title" VARCHAR(20) NOT NULL, "title" VARCHAR(20) NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"user_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE /* User */ "owner_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE /* User */
)""" )"""
) )
@ -43,9 +43,9 @@ def test_create_table():
"name" VARCHAR(200), "name" VARCHAR(200),
"title" VARCHAR(20) NOT NULL, "title" VARCHAR(20) NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"user_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE "owner_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
); );
COMMENT ON COLUMN "category"."user_id" IS 'User'""" COMMENT ON COLUMN "category"."owner_id" IS 'User'"""
) )
@ -137,8 +137,8 @@ def test_set_comment():
ret = Migrate.ddl.set_comment(Category, Category._meta.fields_map.get("name").describe(False)) ret = Migrate.ddl.set_comment(Category, Category._meta.fields_map.get("name").describe(False))
assert ret == 'COMMENT ON COLUMN "category"."name" IS NULL' assert ret == 'COMMENT ON COLUMN "category"."name" IS NULL'
ret = Migrate.ddl.set_comment(Category, Category._meta.fields_map.get("user").describe(False)) ret = Migrate.ddl.set_comment(Category, Category._meta.fields_map.get("owner").describe(False))
assert ret == 'COMMENT ON COLUMN "category"."user_id" IS \'User\'' assert ret == 'COMMENT ON COLUMN "category"."owner_id" IS \'User\''
def test_drop_column(): def test_drop_column():
@ -181,27 +181,27 @@ def test_drop_index():
def test_add_fk(): def test_add_fk():
ret = Migrate.ddl.add_fk( ret = Migrate.ddl.add_fk(
Category, Category._meta.fields_map.get("user").describe(False), User.describe(False) Category, Category._meta.fields_map.get("owner").describe(False), User.describe(False)
) )
if isinstance(Migrate.ddl, MysqlDDL): if isinstance(Migrate.ddl, MysqlDDL):
assert ( assert (
ret ret
== "ALTER TABLE `category` ADD CONSTRAINT `fk_category_user_e2e3874c` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE" == "ALTER TABLE `category` ADD CONSTRAINT `fk_category_user_110d4c63` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) ON DELETE CASCADE"
) )
else: else:
assert ( assert (
ret ret
== 'ALTER TABLE "category" ADD CONSTRAINT "fk_category_user_e2e3874c" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE' == 'ALTER TABLE "category" ADD CONSTRAINT "fk_category_user_110d4c63" FOREIGN KEY ("owner_id") REFERENCES "user" ("id") ON DELETE CASCADE'
) )
def test_drop_fk(): def test_drop_fk():
ret = Migrate.ddl.drop_fk( ret = Migrate.ddl.drop_fk(
Category, Category._meta.fields_map.get("user").describe(False), User.describe(False) Category, Category._meta.fields_map.get("owner").describe(False), User.describe(False)
) )
if isinstance(Migrate.ddl, MysqlDDL): if isinstance(Migrate.ddl, MysqlDDL):
assert ret == "ALTER TABLE `category` DROP FOREIGN KEY `fk_category_user_e2e3874c`" assert ret == "ALTER TABLE `category` DROP FOREIGN KEY `fk_category_user_110d4c63`"
elif isinstance(Migrate.ddl, PostgresDDL): elif isinstance(Migrate.ddl, PostgresDDL):
assert ret == 'ALTER TABLE "category" DROP CONSTRAINT "fk_category_user_e2e3874c"' assert ret == 'ALTER TABLE "category" DROP CONSTRAINT IF EXISTS "fk_category_user_110d4c63"'
else: else:
assert ret == 'ALTER TABLE "category" DROP FOREIGN KEY "fk_category_user_e2e3874c"' assert ret == 'ALTER TABLE "category" DROP FOREIGN KEY "fk_category_user_110d4c63"'

View File

@ -1,5 +1,4 @@
from pathlib import Path from pathlib import Path
from typing import List, cast
import pytest import pytest
import tortoise import tortoise
@ -12,6 +11,9 @@ from aerich.exceptions import NotSupportError
from aerich.migrate import MIGRATE_TEMPLATE, Migrate from aerich.migrate import MIGRATE_TEMPLATE, Migrate
from aerich.utils import get_models_describe from aerich.utils import get_models_describe
# tortoise-orm>=0.21 changes IntField constraints
# from {"ge": 1, "le": 2147483647} to {"ge": -2147483648, "le": 2147483647}
MIN_INT = 1 if tortoise.__version__ < "0.21" else -2147483648
old_models_describe = { old_models_describe = {
"models.Category": { "models.Category": {
"name": "models.Category", "name": "models.Category",
@ -34,7 +36,7 @@ old_models_describe = {
"default": None, "default": None,
"description": None, "description": None,
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
"data_fields": [ "data_fields": [
@ -101,7 +103,7 @@ old_models_describe = {
"default": None, "default": None,
"description": "User", "description": "User",
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
{ {
@ -184,7 +186,7 @@ old_models_describe = {
"default": None, "default": None,
"description": None, "description": None,
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
"data_fields": [ "data_fields": [
@ -291,7 +293,7 @@ old_models_describe = {
"default": None, "default": None,
"description": None, "description": None,
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
"data_fields": [ "data_fields": [
@ -337,7 +339,7 @@ old_models_describe = {
"default": None, "default": None,
"description": None, "description": None,
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
], ],
@ -384,7 +386,7 @@ old_models_describe = {
"default": None, "default": None,
"description": None, "description": None,
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
"data_fields": [ "data_fields": [
@ -578,7 +580,7 @@ old_models_describe = {
"default": None, "default": None,
"description": None, "description": None,
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
"data_fields": [ "data_fields": [
@ -763,7 +765,7 @@ old_models_describe = {
"default": None, "default": None,
"description": None, "description": None,
"docstring": None, "docstring": None,
"constraints": {"ge": 1, "le": 2147483647}, "constraints": {"ge": MIN_INT, "le": 2147483647},
"db_field_types": {"": "INT"}, "db_field_types": {"": "INT"},
}, },
"data_fields": [ "data_fields": [
@ -822,16 +824,6 @@ old_models_describe = {
} }
def should_add_user_id_column_type_alter_sql() -> bool:
if tortoise.__version__ < "0.21":
return False
# tortoise-orm>=0.21 changes IntField constraints
# from {"ge": 1, "le": 2147483647} to {"ge": -2147483648, "le": 2147483647}
data_fields = cast(List[dict], old_models_describe["models.Category"]["data_fields"])
user_id_constraints = data_fields[-1]["constraints"]
return tortoise.fields.data.IntField.constraints != user_id_constraints
def test_migrate(mocker: MockerFixture): def test_migrate(mocker: MockerFixture):
""" """
models.py diff with old_models.py models.py diff with old_models.py
@ -850,8 +842,9 @@ def test_migrate(mocker: MockerFixture):
- drop unique field: Config.name - drop unique field: Config.name
- alter default: Config.status - alter default: Config.status
- rename column: Product.image -> Product.pic - rename column: Product.image -> Product.pic
- rename fk column: Category.user -> Category.owner
""" """
mocker.patch("asyncclick.prompt", side_effect=(True,)) mocker.patch("asyncclick.prompt", side_effect=(True, True))
models_describe = get_models_describe("models") models_describe = get_models_describe("models")
Migrate.app = "models" Migrate.app = "models"
@ -871,6 +864,8 @@ def test_migrate(mocker: MockerFixture):
"ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200)", "ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200)",
"ALTER TABLE `category` MODIFY COLUMN `slug` VARCHAR(100) NOT NULL", "ALTER TABLE `category` MODIFY COLUMN `slug` VARCHAR(100) NOT NULL",
"ALTER TABLE `category` DROP INDEX `title`", "ALTER TABLE `category` DROP INDEX `title`",
"ALTER TABLE `category` RENAME COLUMN `user_id` TO `owner_id`",
"ALTER TABLE `category` ADD CONSTRAINT `fk_category_user_110d4c63` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) ON DELETE CASCADE",
"ALTER TABLE `config` DROP COLUMN `name`", "ALTER TABLE `config` DROP COLUMN `name`",
"ALTER TABLE `config` DROP INDEX `name`", "ALTER TABLE `config` DROP INDEX `name`",
"ALTER TABLE `config` ADD `user_id` INT NOT NULL COMMENT 'User'", "ALTER TABLE `config` ADD `user_id` INT NOT NULL COMMENT 'User'",
@ -878,7 +873,6 @@ def test_migrate(mocker: MockerFixture):
"ALTER TABLE `config` ALTER COLUMN `status` DROP DEFAULT", "ALTER TABLE `config` ALTER COLUMN `status` DROP DEFAULT",
"ALTER TABLE `config` MODIFY COLUMN `value` JSON NOT NULL", "ALTER TABLE `config` MODIFY COLUMN `value` JSON NOT NULL",
"ALTER TABLE `email` ADD `address` VARCHAR(200) NOT NULL", "ALTER TABLE `email` ADD `address` VARCHAR(200) NOT NULL",
"ALTER TABLE `email` DROP COLUMN `user_id`",
"ALTER TABLE `configs` RENAME TO `config`", "ALTER TABLE `configs` RENAME TO `config`",
"ALTER TABLE `product` DROP COLUMN `uuid`", "ALTER TABLE `product` DROP COLUMN `uuid`",
"ALTER TABLE `product` DROP INDEX `uuid`", "ALTER TABLE `product` DROP INDEX `uuid`",
@ -909,9 +903,10 @@ def test_migrate(mocker: MockerFixture):
"ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200) NOT NULL", "ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200) NOT NULL",
"ALTER TABLE `category` MODIFY COLUMN `slug` VARCHAR(200) NOT NULL", "ALTER TABLE `category` MODIFY COLUMN `slug` VARCHAR(200) NOT NULL",
"ALTER TABLE `category` ADD UNIQUE INDEX `title` (`title`)", "ALTER TABLE `category` ADD UNIQUE INDEX `title` (`title`)",
"ALTER TABLE `category` RENAME COLUMN `owner_id` TO `user_id`",
"ALTER TABLE `category` DROP FOREIGN KEY `fk_category_user_110d4c63`",
"ALTER TABLE `config` ADD `name` VARCHAR(100) NOT NULL UNIQUE", "ALTER TABLE `config` ADD `name` VARCHAR(100) NOT NULL UNIQUE",
"ALTER TABLE `config` ADD UNIQUE INDEX `name` (`name`)", "ALTER TABLE `config` ADD UNIQUE INDEX `name` (`name`)",
"ALTER TABLE `config` DROP COLUMN `user_id`",
"ALTER TABLE `config` DROP FOREIGN KEY `fk_config_user_17daa970`", "ALTER TABLE `config` DROP FOREIGN KEY `fk_config_user_17daa970`",
"ALTER TABLE `config` ALTER COLUMN `status` SET DEFAULT 1", "ALTER TABLE `config` ALTER COLUMN `status` SET DEFAULT 1",
"ALTER TABLE `email` ADD `user_id` INT NOT NULL", "ALTER TABLE `email` ADD `user_id` INT NOT NULL",
@ -943,10 +938,6 @@ def test_migrate(mocker: MockerFixture):
"ALTER TABLE `product` MODIFY COLUMN `body` LONGTEXT NOT NULL", "ALTER TABLE `product` MODIFY COLUMN `body` LONGTEXT NOT NULL",
"ALTER TABLE `email` MODIFY COLUMN `is_primary` BOOL NOT NULL DEFAULT 0", "ALTER TABLE `email` MODIFY COLUMN `is_primary` BOOL NOT NULL DEFAULT 0",
} }
if should_add_user_id_column_type_alter_sql():
sql = "ALTER TABLE `category` MODIFY COLUMN `user_id` INT NOT NULL COMMENT 'User'"
expected_upgrade_operators.add(sql)
expected_downgrade_operators.add(sql)
assert not set(Migrate.upgrade_operators).symmetric_difference(expected_upgrade_operators) assert not set(Migrate.upgrade_operators).symmetric_difference(expected_upgrade_operators)
assert not set(Migrate.downgrade_operators).symmetric_difference( assert not set(Migrate.downgrade_operators).symmetric_difference(
@ -959,6 +950,8 @@ def test_migrate(mocker: MockerFixture):
'ALTER TABLE "category" ALTER COLUMN "name" DROP NOT NULL', 'ALTER TABLE "category" ALTER COLUMN "name" DROP NOT NULL',
'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(100) USING "slug"::VARCHAR(100)', 'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(100) USING "slug"::VARCHAR(100)',
'ALTER TABLE "category" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ', 'ALTER TABLE "category" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ',
'ALTER TABLE "category" RENAME COLUMN "user_id" TO "owner_id"',
'ALTER TABLE "category" ADD CONSTRAINT "fk_category_user_110d4c63" FOREIGN KEY ("owner_id") REFERENCES "user" ("id") ON DELETE CASCADE',
'ALTER TABLE "config" DROP COLUMN "name"', 'ALTER TABLE "config" DROP COLUMN "name"',
'DROP INDEX "uid_config_name_2c83c8"', 'DROP INDEX "uid_config_name_2c83c8"',
'ALTER TABLE "config" ADD "user_id" INT NOT NULL', 'ALTER TABLE "config" ADD "user_id" INT NOT NULL',
@ -967,7 +960,6 @@ def test_migrate(mocker: MockerFixture):
'ALTER TABLE "config" ALTER COLUMN "value" TYPE JSONB USING "value"::JSONB', 'ALTER TABLE "config" ALTER COLUMN "value" TYPE JSONB USING "value"::JSONB',
'ALTER TABLE "configs" RENAME TO "config"', 'ALTER TABLE "configs" RENAME TO "config"',
'ALTER TABLE "email" ADD "address" VARCHAR(200) NOT NULL', 'ALTER TABLE "email" ADD "address" VARCHAR(200) NOT NULL',
'ALTER TABLE "email" DROP COLUMN "user_id"',
'ALTER TABLE "email" RENAME COLUMN "id" TO "email_id"', 'ALTER TABLE "email" RENAME COLUMN "id" TO "email_id"',
'ALTER TABLE "email" ALTER COLUMN "is_primary" TYPE BOOL USING "is_primary"::BOOL', 'ALTER TABLE "email" ALTER COLUMN "is_primary" TYPE BOOL USING "is_primary"::BOOL',
'DROP INDEX "uid_product_uuid_d33c18"', 'DROP INDEX "uid_product_uuid_d33c18"',
@ -997,11 +989,12 @@ def test_migrate(mocker: MockerFixture):
'ALTER TABLE "category" ALTER COLUMN "name" SET NOT NULL', 'ALTER TABLE "category" ALTER COLUMN "name" SET NOT NULL',
'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(200) USING "slug"::VARCHAR(200)', 'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(200) USING "slug"::VARCHAR(200)',
'ALTER TABLE "category" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ', 'ALTER TABLE "category" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ',
'ALTER TABLE "category" RENAME COLUMN "owner_id" TO "user_id"',
'ALTER TABLE "category" DROP CONSTRAINT IF EXISTS "fk_category_user_110d4c63"',
'ALTER TABLE "config" ADD "name" VARCHAR(100) NOT NULL UNIQUE', 'ALTER TABLE "config" ADD "name" VARCHAR(100) NOT NULL UNIQUE',
'CREATE UNIQUE INDEX "uid_config_name_2c83c8" ON "config" ("name")', 'CREATE UNIQUE INDEX "uid_config_name_2c83c8" ON "config" ("name")',
'ALTER TABLE "config" ALTER COLUMN "status" SET DEFAULT 1', 'ALTER TABLE "config" ALTER COLUMN "status" SET DEFAULT 1',
'ALTER TABLE "config" DROP COLUMN "user_id"', 'ALTER TABLE "config" DROP CONSTRAINT IF EXISTS "fk_config_user_17daa970"',
'ALTER TABLE "config" DROP CONSTRAINT "fk_config_user_17daa970"',
'ALTER TABLE "config" RENAME TO "configs"', 'ALTER TABLE "config" RENAME TO "configs"',
'ALTER TABLE "config" ALTER COLUMN "value" TYPE JSONB USING "value"::JSONB', 'ALTER TABLE "config" ALTER COLUMN "value" TYPE JSONB USING "value"::JSONB',
'ALTER TABLE "email" ADD "user_id" INT NOT NULL', 'ALTER TABLE "email" ADD "user_id" INT NOT NULL',
@ -1030,10 +1023,6 @@ def test_migrate(mocker: MockerFixture):
'DROP TABLE IF EXISTS "email_user"', 'DROP TABLE IF EXISTS "email_user"',
'DROP TABLE IF EXISTS "newmodel"', 'DROP TABLE IF EXISTS "newmodel"',
} }
if should_add_user_id_column_type_alter_sql():
sql = 'ALTER TABLE "category" ALTER COLUMN "user_id" TYPE INT USING "user_id"::INT'
expected_upgrade_operators.add(sql)
expected_downgrade_operators.add(sql)
assert not set(Migrate.upgrade_operators).symmetric_difference(expected_upgrade_operators) assert not set(Migrate.upgrade_operators).symmetric_difference(expected_upgrade_operators)
assert not set(Migrate.downgrade_operators).symmetric_difference( assert not set(Migrate.downgrade_operators).symmetric_difference(
expected_downgrade_operators expected_downgrade_operators