From 9879004fee03ae2787227137a17aa7684c84a7e4 Mon Sep 17 00:00:00 2001 From: long2ice Date: Thu, 19 Nov 2020 10:11:52 +0800 Subject: [PATCH] Add `rename` column support MySQL5 --- CHANGELOG.md | 1 + Makefile | 2 ++ README.md | 4 ++++ aerich/cli.py | 13 +++++++++++-- aerich/ddl/__init__.py | 13 +++++++++++++ aerich/migrate.py | 39 +++++++++++++++++++++++++++++++++------ conftest.py | 2 +- pyproject.toml | 2 +- 8 files changed, 66 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be20157..72f2792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### 0.4.0 - Use `.sql` instead of `.json` to store version file. +- Add `rename` column support MySQL5. ## 0.3 diff --git a/Makefile b/Makefile index 7ddf48e..59102b7 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,10 @@ black_opts = -l 100 -t py38 py_warn = PYTHONDEVMODE=1 MYSQL_HOST ?= "127.0.0.1" MYSQL_PORT ?= 3306 +MYSQL_PASS ?= "123456" POSTGRES_HOST ?= "127.0.0.1" POSTGRES_PORT ?= 5432 +POSTGRES_PAS ?= "123456" help: @echo "Aerich development makefile" diff --git a/README.md b/README.md index 61dda37..2868a7a 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,10 @@ Usage: aerich downgrade [OPTIONS] Options: -v, --version INTEGER Specified version, default to last. [default: -1] + -d, --delete Delete version files at the same time. [default: + False] + + --yes Confirm the action without prompting. -h, --help Show this message and exit. ``` diff --git a/aerich/cli.py b/aerich/cli.py index 1b485e4..ae50eb5 100644 --- a/aerich/cli.py +++ b/aerich/cli.py @@ -135,12 +135,20 @@ async def upgrade(ctx: Context): show_default=True, help="Specified version, default to last.", ) +@click.option( + "-d", + "--delete", + is_flag=True, + default=False, + show_default=True, + help="Delete version files at the same time.", +) @click.pass_context @click.confirmation_option( prompt="Downgrade is dangerous, which maybe lose your data, are you sure?", ) @coro -async def downgrade(ctx: Context, version: int): +async def downgrade(ctx: Context, version: int, delete: bool): app = ctx.obj["app"] config = ctx.obj["config"] if version == -1: @@ -164,7 +172,8 @@ async def downgrade(ctx: Context, version: int): for downgrade_query in downgrade_query_list: await conn.execute_query(downgrade_query) await version.delete() - os.unlink(file_path) + if delete: + os.unlink(file_path) click.secho(f"Success downgrade {file}", fg=Color.green) diff --git a/aerich/ddl/__init__.py b/aerich/ddl/__init__.py index 9ed462e..b39c644 100644 --- a/aerich/ddl/__init__.py +++ b/aerich/ddl/__init__.py @@ -22,6 +22,9 @@ class BaseDDL: _DROP_FK_TEMPLATE = 'ALTER TABLE "{table_name}" DROP FOREIGN KEY "{fk_name}"' _M2M_TABLE_TEMPLATE = 'CREATE TABLE "{table_name}" ("{backward_key}" {backward_type} NOT NULL REFERENCES "{backward_table}" ("{backward_field}") ON DELETE CASCADE,"{forward_key}" {forward_type} NOT NULL REFERENCES "{forward_table}" ("{forward_field}") ON DELETE {on_delete}){extra}{comment};' _MODIFY_COLUMN_TEMPLATE = 'ALTER TABLE "{table_name}" MODIFY COLUMN {column}' + _CHANGE_COLUMN_TEMPLATE = ( + 'ALTER TABLE "{table_name}" CHANGE {old_column_name} {new_column_name} {new_column_type}' + ) def __init__(self, client: "BaseDBAsyncClient"): self.client = client @@ -136,6 +139,16 @@ class BaseDDL: new_column_name=new_column_name, ) + def change_column( + self, model: "Type[Model]", old_column_name: str, new_column_name: str, new_column_type: str + ): + return self._CHANGE_COLUMN_TEMPLATE.format( + table_name=model._meta.db_table, + old_column_name=old_column_name, + new_column_name=new_column_name, + new_column_type=new_column_type, + ) + def add_index(self, model: "Type[Model]", field_names: List[str], unique=False): return self._ADD_INDEX_TEMPLATE.format( unique="UNIQUE" if unique else "", diff --git a/aerich/migrate.py b/aerich/migrate.py index 9a26053..98d67c4 100644 --- a/aerich/migrate.py +++ b/aerich/migrate.py @@ -4,12 +4,15 @@ import re from datetime import datetime from importlib import import_module from io import StringIO -from typing import Dict, List, Optional, Tuple, Type +from typing import Dict, List, Optional, Tuple, Type, Union import click +from packaging import version +from packaging.version import LegacyVersion, Version from tortoise import ( BackwardFKRelation, BackwardOneToOneRelation, + BaseDBAsyncClient, ForeignKeyFieldInstance, ManyToManyFieldInstance, Model, @@ -41,6 +44,7 @@ class Migrate: app: str migrate_location: str dialect: str + _db_version: Union[LegacyVersion, Version] = None @classmethod def get_old_model_file(cls, app: str, location: str): @@ -67,6 +71,13 @@ class Migrate: except (OSError, FileNotFoundError): pass + @classmethod + async def _get_db_version(cls, connection: BaseDBAsyncClient): + if cls.dialect == "mysql": + sql = "select version() as version" + ret = await connection.execute_query(sql) + cls._db_version = version.parse(ret[1][0].get("version")) + @classmethod async def init_with_old_models(cls, config: dict, app: str, location: str): await Tortoise.init(config=config) @@ -83,7 +94,6 @@ class Migrate: await Tortoise.init(config=migrate_config) connection = get_app_connection(config, app) - cls.dialect = connection.schema_generator.DIALECT if cls.dialect == "mysql": from aerich.ddl.mysql import MysqlDDL @@ -96,6 +106,8 @@ class Migrate: from aerich.ddl.postgres import PostgresDDL cls.ddl = PostgresDDL(connection) + cls.dialect = cls.ddl.DIALECT + await cls._get_db_version(connection) @classmethod async def _get_last_version_num(cls): @@ -300,10 +312,16 @@ class Migrate: else: is_rename = diff_key in cls._rename_new if is_rename: - cls._add_operator( - cls._rename_field(new_model, old_field, new_field), - upgrade, - ) + if cls.dialect == "mysql" and cls._db_version.major == 5: + cls._add_operator( + cls._change_field(new_model, old_field, new_field), + upgrade, + ) + else: + cls._add_operator( + cls._rename_field(new_model, old_field, new_field), + upgrade, + ) break else: cls._add_operator( @@ -487,6 +505,15 @@ class Migrate: def _rename_field(cls, model: Type[Model], old_field: Field, new_field: Field): return cls.ddl.rename_column(model, old_field.model_field_name, new_field.model_field_name) + @classmethod + def _change_field(cls, model: Type[Model], old_field: Field, new_field: Field): + return cls.ddl.change_column( + model, + old_field.model_field_name, + new_field.model_field_name, + new_field.get_for_dialect(cls.dialect, "SQL_TYPE"), + ) + @classmethod def _add_fk(cls, model: Type[Model], field: ForeignKeyFieldInstance): """ diff --git a/conftest.py b/conftest.py index 5a0a224..403ced8 100644 --- a/conftest.py +++ b/conftest.py @@ -67,5 +67,5 @@ async def initialize_tests(event_loop, request): Migrate.ddl = SqliteDDL(client) elif client.schema_generator is AsyncpgSchemaGenerator: Migrate.ddl = PostgresDDL(client) - + Migrate.dialect = Migrate.ddl.DIALECT request.addfinalizer(lambda: event_loop.run_until_complete(Tortoise._drop_databases())) diff --git a/pyproject.toml b/pyproject.toml index 3267f4f..655f7f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aerich" -version = "0.3.3" +version = "0.4.0" description = "A database migrations tool for Tortoise ORM." authors = ["long2ice "] license = "Apache-2.0"