fix: aerich migrate raises tortoise.exceptions.FieldError when index.INDEX_TYPE is not empty (#415)
* fix: aerich migrate raises `tortoise.exceptions.FieldError` when `index.INDEX_TYPE` is not empty * feat: add `IF NOT EXISTS` to postgres create index template * chore: explicit declare type hints of function parameters
This commit is contained in:
@@ -44,3 +44,27 @@ async def init_db(tortoise_orm, generate_schemas=True) -> None:
|
||||
def copy_files(*src_files: Path, target_dir: Path) -> None:
|
||||
for src in src_files:
|
||||
shutil.copy(src, target_dir)
|
||||
|
||||
|
||||
class Dialect:
|
||||
test_db_url: str
|
||||
|
||||
@classmethod
|
||||
def load_env(cls) -> None:
|
||||
if getattr(cls, "test_db_url", None) is None:
|
||||
cls.test_db_url = os.getenv("TEST_DB", "")
|
||||
|
||||
@classmethod
|
||||
def is_postgres(cls) -> bool:
|
||||
cls.load_env()
|
||||
return "postgres" in cls.test_db_url
|
||||
|
||||
@classmethod
|
||||
def is_mysql(cls) -> bool:
|
||||
cls.load_env()
|
||||
return "mysql" in cls.test_db_url
|
||||
|
||||
@classmethod
|
||||
def is_sqlite(cls) -> bool:
|
||||
cls.load_env()
|
||||
return not cls.test_db_url or "sqlite" in cls.test_db_url
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import uuid
|
||||
from enum import IntEnum
|
||||
|
||||
from tortoise import Model, fields
|
||||
from tortoise.contrib.mysql.indexes import FullTextIndex
|
||||
from tortoise.contrib.postgres.indexes import HashIndex
|
||||
from tortoise.indexes import Index
|
||||
|
||||
from tests._utils import Dialect
|
||||
from tests.indexes import CustomIndex
|
||||
|
||||
|
||||
@@ -63,6 +68,14 @@ class Category(Model):
|
||||
title = fields.CharField(max_length=20, unique=False)
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
if Dialect.is_postgres():
|
||||
indexes = [HashIndex(fields=("slug",))]
|
||||
elif Dialect.is_mysql():
|
||||
indexes = [FullTextIndex(fields=("slug",))] # type:ignore
|
||||
else:
|
||||
indexes = [Index(fields=("slug",))] # type:ignore
|
||||
|
||||
|
||||
class Product(Model):
|
||||
categories: fields.ManyToManyRelation[Category] = fields.ManyToManyField(
|
||||
@@ -75,7 +88,7 @@ class Product(Model):
|
||||
view_num = fields.IntField(description="View Num", default=0)
|
||||
sort = fields.IntField()
|
||||
is_reviewed = fields.BooleanField(description="Is Reviewed")
|
||||
type = fields.IntEnumField(
|
||||
type: int = fields.IntEnumField(
|
||||
ProductType, description="Product Type", source_field="type_db_alias"
|
||||
)
|
||||
pic = fields.CharField(max_length=200)
|
||||
|
||||
@@ -56,7 +56,7 @@ class Product(Model):
|
||||
view_num = fields.IntField(description="View Num")
|
||||
sort = fields.IntField()
|
||||
is_reviewed = fields.BooleanField(description="Is Reviewed")
|
||||
type = fields.IntEnumField(
|
||||
type: int = fields.IntEnumField(
|
||||
ProductType, description="Product Type", source_field="type_db_alias"
|
||||
)
|
||||
image = fields.CharField(max_length=200)
|
||||
|
||||
@@ -55,6 +55,9 @@ class Category(Model):
|
||||
title = fields.CharField(max_length=20, unique=True)
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [Index(fields=("slug",))]
|
||||
|
||||
|
||||
class Product(Model):
|
||||
categories: fields.ManyToManyRelation[Category] = fields.ManyToManyField("models.Category")
|
||||
@@ -63,7 +66,7 @@ class Product(Model):
|
||||
view_num = fields.IntField(description="View Num")
|
||||
sort = fields.IntField()
|
||||
is_review = fields.BooleanField(description="Is Reviewed")
|
||||
type = fields.IntEnumField(
|
||||
type: int = fields.IntEnumField(
|
||||
ProductType, description="Product Type", source_field="type_db_alias"
|
||||
)
|
||||
image = fields.CharField(max_length=200)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import tortoise
|
||||
|
||||
from aerich.ddl.mysql import MysqlDDL
|
||||
from aerich.ddl.postgres import PostgresDDL
|
||||
from aerich.ddl.sqlite import SqliteDDL
|
||||
@@ -8,6 +10,21 @@ from tests.models import Category, Product, User
|
||||
def test_create_table():
|
||||
ret = Migrate.ddl.create_table(Category)
|
||||
if isinstance(Migrate.ddl, MysqlDDL):
|
||||
if tortoise.__version__ >= "0.24":
|
||||
assert (
|
||||
ret
|
||||
== """CREATE TABLE IF NOT EXISTS `category` (
|
||||
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`slug` VARCHAR(100) NOT NULL,
|
||||
`name` VARCHAR(200),
|
||||
`title` VARCHAR(20) NOT NULL,
|
||||
`created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`owner_id` INT NOT NULL COMMENT 'User',
|
||||
CONSTRAINT `fk_category_user_110d4c63` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) ON DELETE CASCADE,
|
||||
FULLTEXT KEY `idx_category_slug_e9bcff` (`slug`)
|
||||
) CHARACTER SET utf8mb4"""
|
||||
)
|
||||
return
|
||||
assert (
|
||||
ret
|
||||
== """CREATE TABLE IF NOT EXISTS `category` (
|
||||
@@ -18,20 +35,23 @@ def test_create_table():
|
||||
`created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`owner_id` INT NOT NULL COMMENT 'User',
|
||||
CONSTRAINT `fk_category_user_110d4c63` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
|
||||
) CHARACTER SET utf8mb4"""
|
||||
) CHARACTER SET utf8mb4;
|
||||
CREATE FULLTEXT INDEX `idx_category_slug_e9bcff` ON `category` (`slug`)"""
|
||||
)
|
||||
|
||||
elif isinstance(Migrate.ddl, SqliteDDL):
|
||||
exists = "IF NOT EXISTS " if tortoise.__version__ >= "0.24" else ""
|
||||
assert (
|
||||
ret
|
||||
== """CREATE TABLE IF NOT EXISTS "category" (
|
||||
== f"""CREATE TABLE IF NOT EXISTS "category" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
"slug" VARCHAR(100) NOT NULL,
|
||||
"name" VARCHAR(200),
|
||||
"title" VARCHAR(20) NOT NULL,
|
||||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"owner_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE /* User */
|
||||
)"""
|
||||
);
|
||||
CREATE INDEX {exists}"idx_category_slug_e9bcff" ON "category" ("slug")"""
|
||||
)
|
||||
|
||||
elif isinstance(Migrate.ddl, PostgresDDL):
|
||||
@@ -45,6 +65,7 @@ def test_create_table():
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"owner_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_category_slug_e9bcff" ON "category" USING HASH ("slug");
|
||||
COMMENT ON COLUMN "category"."owner_id" IS 'User'"""
|
||||
)
|
||||
|
||||
@@ -163,6 +184,14 @@ def test_add_index():
|
||||
if isinstance(Migrate.ddl, MysqlDDL):
|
||||
assert index == "ALTER TABLE `category` ADD INDEX `idx_category_name_8b0cb9` (`name`)"
|
||||
assert index_u == "ALTER TABLE `category` ADD UNIQUE INDEX `name` (`name`)"
|
||||
elif isinstance(Migrate.ddl, PostgresDDL):
|
||||
assert (
|
||||
index == 'CREATE INDEX IF NOT EXISTS "idx_category_name_8b0cb9" ON "category" ("name")'
|
||||
)
|
||||
assert (
|
||||
index_u
|
||||
== 'CREATE UNIQUE INDEX IF NOT EXISTS "uid_category_name_8b0cb9" ON "category" ("name")'
|
||||
)
|
||||
else:
|
||||
assert index == 'CREATE INDEX "idx_category_name_8b0cb9" ON "category" ("name")'
|
||||
assert index_u == 'CREATE UNIQUE INDEX "uid_category_name_8b0cb9" ON "category" ("name")'
|
||||
|
||||
@@ -35,7 +35,7 @@ old_models_describe = {
|
||||
"description": None,
|
||||
"docstring": None,
|
||||
"unique_together": [],
|
||||
"indexes": [],
|
||||
"indexes": [describe_index(Index(fields=("slug",)))],
|
||||
"pk_field": {
|
||||
"name": "id",
|
||||
"field_type": "IntField",
|
||||
@@ -929,6 +929,7 @@ def test_migrate(mocker: MockerFixture):
|
||||
- drop fk field: Email.user
|
||||
- drop field: User.avatar
|
||||
- add index: Email.email
|
||||
- change index type for indexed field: Email.slug
|
||||
- add many to many: Email.users
|
||||
- add one to one: Email.config
|
||||
- remove unique: Category.title
|
||||
@@ -965,6 +966,8 @@ def test_migrate(mocker: MockerFixture):
|
||||
"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 `category` ADD FULLTEXT INDEX `idx_category_slug_e9bcff` (`slug`)",
|
||||
"ALTER TABLE `category` DROP INDEX `idx_category_slug_e9bcff`",
|
||||
"ALTER TABLE `email` DROP COLUMN `user_id`",
|
||||
"ALTER TABLE `config` DROP COLUMN `name`",
|
||||
"ALTER TABLE `config` DROP INDEX `name`",
|
||||
@@ -1007,6 +1010,8 @@ def test_migrate(mocker: MockerFixture):
|
||||
"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 `category` ADD INDEX `idx_category_slug_e9bcff` (`slug`)",
|
||||
"ALTER TABLE `category` DROP INDEX `idx_category_slug_e9bcff`",
|
||||
"ALTER TABLE `config` ADD `name` VARCHAR(100) NOT NULL UNIQUE",
|
||||
"ALTER TABLE `config` ADD UNIQUE INDEX `name` (`name`)",
|
||||
"ALTER TABLE `config` DROP FOREIGN KEY `fk_config_user_17daa970`",
|
||||
@@ -1050,6 +1055,8 @@ def test_migrate(mocker: MockerFixture):
|
||||
'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(100) USING "slug"::VARCHAR(100)',
|
||||
'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',
|
||||
'CREATE INDEX IF NOT EXISTS "idx_category_slug_e9bcff" ON "category" USING HASH ("slug")',
|
||||
'DROP INDEX IF EXISTS "idx_category_slug_e9bcff"',
|
||||
'ALTER TABLE "config" DROP COLUMN "name"',
|
||||
'DROP INDEX IF EXISTS "uid_config_name_2c83c8"',
|
||||
'ALTER TABLE "config" ADD "user_id" INT NOT NULL',
|
||||
@@ -1070,12 +1077,12 @@ def test_migrate(mocker: MockerFixture):
|
||||
'ALTER TABLE "user" ALTER COLUMN "password" TYPE VARCHAR(100) USING "password"::VARCHAR(100)',
|
||||
'ALTER TABLE "user" DROP COLUMN "avatar"',
|
||||
'ALTER TABLE "user" ALTER COLUMN "longitude" TYPE DECIMAL(10,8) USING "longitude"::DECIMAL(10,8)',
|
||||
'CREATE INDEX "idx_product_name_869427" ON "product" ("name", "type_db_alias")',
|
||||
'CREATE INDEX "idx_email_email_4a1a33" ON "email" ("email")',
|
||||
'CREATE INDEX IF NOT EXISTS "idx_product_name_869427" ON "product" ("name", "type_db_alias")',
|
||||
'CREATE INDEX IF NOT EXISTS "idx_email_email_4a1a33" ON "email" ("email")',
|
||||
'CREATE TABLE "email_user" (\n "email_id" INT NOT NULL REFERENCES "email" ("email_id") ON DELETE CASCADE,\n "user_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE\n)',
|
||||
'CREATE TABLE IF NOT EXISTS "newmodel" (\n "id" SERIAL NOT NULL PRIMARY KEY,\n "name" VARCHAR(50) NOT NULL\n);\nCOMMENT ON COLUMN "config"."user_id" IS \'User\'',
|
||||
'CREATE UNIQUE INDEX "uid_product_name_869427" ON "product" ("name", "type_db_alias")',
|
||||
'CREATE UNIQUE INDEX "uid_user_usernam_9987ab" ON "user" ("username")',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "uid_product_name_869427" ON "product" ("name", "type_db_alias")',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "uid_user_usernam_9987ab" ON "user" ("username")',
|
||||
'CREATE TABLE "product_user" (\n "product_id" INT NOT NULL REFERENCES "product" ("id") ON DELETE CASCADE,\n "user_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE\n)',
|
||||
'CREATE TABLE "config_category_map" (\n "category_id" INT NOT NULL REFERENCES "category" ("id") ON DELETE CASCADE,\n "config_id" INT NOT NULL REFERENCES "config" ("id") ON DELETE CASCADE\n)',
|
||||
'DROP TABLE IF EXISTS "config_category"',
|
||||
@@ -1087,13 +1094,15 @@ def test_migrate(mocker: MockerFixture):
|
||||
assert not upgrade_less_than_expected
|
||||
|
||||
expected_downgrade_operators = {
|
||||
'CREATE UNIQUE INDEX "uid_category_title_f7fc03" ON "category" ("title")',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "uid_category_title_f7fc03" ON "category" ("title")',
|
||||
'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" RENAME COLUMN "owner_id" TO "user_id"',
|
||||
'ALTER TABLE "category" DROP CONSTRAINT IF EXISTS "fk_category_user_110d4c63"',
|
||||
'DROP INDEX IF EXISTS "idx_category_slug_e9bcff"',
|
||||
'CREATE INDEX IF NOT EXISTS "idx_category_slug_e9bcff" ON "category" ("slug")',
|
||||
'ALTER TABLE "config" ADD "name" VARCHAR(100) NOT NULL UNIQUE',
|
||||
'CREATE UNIQUE INDEX "uid_config_name_2c83c8" ON "config" ("name")',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "uid_config_name_2c83c8" ON "config" ("name")',
|
||||
'ALTER TABLE "config" ALTER COLUMN "status" SET DEFAULT 1',
|
||||
'ALTER TABLE "config" DROP CONSTRAINT IF EXISTS "fk_config_user_17daa970"',
|
||||
'ALTER TABLE "config" RENAME TO "configs"',
|
||||
@@ -1104,7 +1113,7 @@ def test_migrate(mocker: MockerFixture):
|
||||
'ALTER TABLE "email" DROP COLUMN "config_id"',
|
||||
'ALTER TABLE "email" DROP CONSTRAINT IF EXISTS "fk_email_config_76a9dc71"',
|
||||
'ALTER TABLE "product" ADD "uuid" INT NOT NULL UNIQUE',
|
||||
'CREATE UNIQUE INDEX "uid_product_uuid_d33c18" ON "product" ("uuid")',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "uid_product_uuid_d33c18" ON "product" ("uuid")',
|
||||
'ALTER TABLE "product" ALTER COLUMN "view_num" DROP DEFAULT',
|
||||
'ALTER TABLE "product" RENAME COLUMN "pic" TO "image"',
|
||||
'ALTER TABLE "product" RENAME COLUMN "is_deleted" TO "is_delete"',
|
||||
|
||||
Reference in New Issue
Block a user