From 2aa5e053747ed3301022c259e806d202184e6988 Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Wed, 8 Jan 2025 22:01:10 +0800 Subject: [PATCH 1/7] fix: mysql generated schema syntax error --- tortoise/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tortoise/utils.py b/tortoise/utils.py index e5c4fe0dd..3ea55848c 100644 --- a/tortoise/utils.py +++ b/tortoise/utils.py @@ -37,9 +37,11 @@ async def generate_schema_for_client(client: "BaseDBAsyncClient", safe: bool) -> :param safe: When set to true, creates the table only when it does not already exist. """ generator = client.schema_generator(client) - schema = get_schema_sql(client, safe) - logger.debug("Creating schema: %s", schema) - if schema: # pragma: nobranch + if schema := generator.get_create_schema_sql(safe): # pragma: nobranch + if client.schema_generator.DIALECT == "mysql": + # MySQL does not support 'IF NOT EXISTS' syntax for `INDEX` + schema = schema.replace(" INDEX IF NOT EXISTS", " INDEX") + logger.debug("Creating schema: %s", schema) await generator.generate_from_string(schema) From b319db3b3fe51e737936bf83f596c25ac3913f06 Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Thu, 9 Jan 2025 14:31:40 +0800 Subject: [PATCH 2/7] tests: add test case for Index --- tests/testmodels.py | 4 ++++ tests/utils/test_describe_model.py | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/testmodels.py b/tests/testmodels.py index 1e2c7dcf6..723b88c54 100644 --- a/tests/testmodels.py +++ b/tests/testmodels.py @@ -17,6 +17,7 @@ from tortoise import fields from tortoise.exceptions import ValidationError from tortoise.fields import NO_ACTION +from tortoise.indexes import Index from tortoise.manager import Manager from tortoise.models import Model from tortoise.queryset import QuerySet @@ -150,6 +151,9 @@ class ModelTestPydanticMetaBackwardRelations3(Model): class Node(Model): name = fields.CharField(max_length=10) + class Meta: + indexes = [Index(fields=("name",))] + class Tree(Model): parent: fields.ForeignKeyRelation[Node] = fields.ForeignKeyField( diff --git a/tests/utils/test_describe_model.py b/tests/utils/test_describe_model.py index 998d6e794..a25d114b2 100644 --- a/tests/utils/test_describe_model.py +++ b/tests/utils/test_describe_model.py @@ -25,12 +25,19 @@ ManyToManyFieldInstance, OneToOneFieldInstance, ) +from tortoise.indexes import Index + + +def dump_index(obj): + if isinstance(obj, Index): + return repr(obj) + return obj class TestDescribeModels(test.TestCase): def test_describe_models_all_serializable(self): val = Tortoise.describe_models() - json.dumps(val) + json.dumps(val, default=dump_index) self.assertIn("models.SourceFields", val.keys()) self.assertIn("models.Event", val.keys()) From fe2fcbfe5e4c256d0baa6ad74bae5d4caa66f277 Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Thu, 9 Jan 2025 15:27:09 +0800 Subject: [PATCH 3/7] refactor: change index sql in its method --- tortoise/indexes.py | 4 ++-- tortoise/utils.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tortoise/indexes.py b/tortoise/indexes.py index 96222e429..b7e8883e5 100644 --- a/tortoise/indexes.py +++ b/tortoise/indexes.py @@ -58,9 +58,9 @@ def get_sql( else: expressions = [f"({expression.get_sql()})" for expression in self.expressions] fields = ", ".join(expressions) - + exists = "IF NOT EXISTS " if safe and schema_generator.DIALECT != "mysql" else "" return self.INDEX_CREATE_TEMPLATE.format( - exists="IF NOT EXISTS " if safe else "", + exists=exists, index_name=schema_generator.quote(self.index_name(schema_generator, model)), index_type=f" {self.INDEX_TYPE} ", table_name=schema_generator.quote(model._meta.db_table), diff --git a/tortoise/utils.py b/tortoise/utils.py index 3ea55848c..8ea411b39 100644 --- a/tortoise/utils.py +++ b/tortoise/utils.py @@ -38,9 +38,6 @@ async def generate_schema_for_client(client: "BaseDBAsyncClient", safe: bool) -> """ generator = client.schema_generator(client) if schema := generator.get_create_schema_sql(safe): # pragma: nobranch - if client.schema_generator.DIALECT == "mysql": - # MySQL does not support 'IF NOT EXISTS' syntax for `INDEX` - schema = schema.replace(" INDEX IF NOT EXISTS", " INDEX") logger.debug("Creating schema: %s", schema) await generator.generate_from_string(schema) From 7fd474262d6c3edcb076e828f970081634fcc5f6 Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Thu, 9 Jan 2025 15:47:10 +0800 Subject: [PATCH 4/7] Remove extra space --- tests/schema/test_generate_schema.py | 4 ++-- tortoise/indexes.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/schema/test_generate_schema.py b/tests/schema/test_generate_schema.py index 40272d8e2..9beebc2c3 100644 --- a/tests/schema/test_generate_schema.py +++ b/tests/schema/test_generate_schema.py @@ -1102,7 +1102,7 @@ async def test_index_unsafe(self): CREATE INDEX "idx_index_gist_c807bf" ON "index" USING GIST ("gist"); CREATE INDEX "idx_index_sp_gist_2c0bad" ON "index" USING SPGIST ("sp_gist"); CREATE INDEX "idx_index_hash_cfe6b5" ON "index" USING HASH ("hash"); -CREATE INDEX "idx_index_partial_c5be6a" ON "index" USING ("partial") WHERE id = 1;""", +CREATE INDEX "idx_index_partial_c5be6a" ON "index" USING ("partial") WHERE id = 1;""", ) async def test_index_safe(self): @@ -1126,7 +1126,7 @@ async def test_index_safe(self): CREATE INDEX IF NOT EXISTS "idx_index_gist_c807bf" ON "index" USING GIST ("gist"); CREATE INDEX IF NOT EXISTS "idx_index_sp_gist_2c0bad" ON "index" USING SPGIST ("sp_gist"); CREATE INDEX IF NOT EXISTS "idx_index_hash_cfe6b5" ON "index" USING HASH ("hash"); -CREATE INDEX IF NOT EXISTS "idx_index_partial_c5be6a" ON "index" USING ("partial") WHERE id = 1;""", +CREATE INDEX IF NOT EXISTS "idx_index_partial_c5be6a" ON "index" USING ("partial") WHERE id = 1;""", ) async def test_m2m_no_auto_create(self): diff --git a/tortoise/indexes.py b/tortoise/indexes.py index b7e8883e5..f2a8458e3 100644 --- a/tortoise/indexes.py +++ b/tortoise/indexes.py @@ -59,10 +59,11 @@ def get_sql( expressions = [f"({expression.get_sql()})" for expression in self.expressions] fields = ", ".join(expressions) exists = "IF NOT EXISTS " if safe and schema_generator.DIALECT != "mysql" else "" + index_type = f" {self.INDEX_TYPE} " if self.INDEX_TYPE else " " return self.INDEX_CREATE_TEMPLATE.format( exists=exists, index_name=schema_generator.quote(self.index_name(schema_generator, model)), - index_type=f" {self.INDEX_TYPE} ", + index_type=index_type, table_name=schema_generator.quote(model._meta.db_table), fields=fields, extra=self.extra, From 166cb3cc4e867c8dec538e417ec9f6e0b89509da Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Thu, 9 Jan 2025 16:33:52 +0800 Subject: [PATCH 5/7] refactor: use dialect special style for index --- tortoise/indexes.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tortoise/indexes.py b/tortoise/indexes.py index f2a8458e3..d6e268f4b 100644 --- a/tortoise/indexes.py +++ b/tortoise/indexes.py @@ -15,6 +15,11 @@ class Index: "CREATE{index_type}INDEX {exists}{index_name} ON {table_name} ({fields}){extra};" ) + class _db_mysql: + INDEX_CREATE_TEMPLATE = ( + "CREATE{index_type}INDEX {index_name} ON {table_name} ({fields}){extra};" + ) + def __init__( self, *expressions: Term, @@ -50,6 +55,12 @@ def __repr__(self) -> str: argument += f", {name=}" return self.__class__.__name__ + "(" + argument + ")" + def get_for_dialect(self, dialect: str, key: str) -> Any: + value = getattr(self, key, "") + if dialect_special := getattr(self, f"_db_{dialect.lower()}", None): + return getattr(dialect_special, key, value) + return value + def get_sql( self, schema_generator: "BaseSchemaGenerator", model: "Type[Model]", safe: bool ) -> str: @@ -58,12 +69,11 @@ def get_sql( else: expressions = [f"({expression.get_sql()})" for expression in self.expressions] fields = ", ".join(expressions) - exists = "IF NOT EXISTS " if safe and schema_generator.DIALECT != "mysql" else "" - index_type = f" {self.INDEX_TYPE} " if self.INDEX_TYPE else " " - return self.INDEX_CREATE_TEMPLATE.format( - exists=exists, + template = self.get_for_dialect(schema_generator.DIALECT, "INDEX_CREATE_TEMPLATE") + return template.format( + exists="IF NOT EXISTS " if safe else "", index_name=schema_generator.quote(self.index_name(schema_generator, model)), - index_type=index_type, + index_type=f" {self.INDEX_TYPE} " if self.INDEX_TYPE else " ", table_name=schema_generator.quote(model._meta.db_table), fields=fields, extra=self.extra, From e93fd7eb1522922c4573b00cf8232cc628c424a7 Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Thu, 9 Jan 2025 17:04:52 +0800 Subject: [PATCH 6/7] fix unittest error --- tests/schema/test_generate_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/schema/test_generate_schema.py b/tests/schema/test_generate_schema.py index 9beebc2c3..269045f45 100644 --- a/tests/schema/test_generate_schema.py +++ b/tests/schema/test_generate_schema.py @@ -726,8 +726,8 @@ async def test_index_safe(self): `full_text` LONGTEXT NOT NULL, `geometry` GEOMETRY NOT NULL ) CHARACTER SET utf8mb4; -CREATE FULLTEXT INDEX IF NOT EXISTS `idx_index_full_te_3caba4` ON `index` (`full_text`) WITH PARSER ngram; -CREATE SPATIAL INDEX IF NOT EXISTS `idx_index_geometr_0b4dfb` ON `index` (`geometry`);""", +CREATE FULLTEXT INDEX `idx_index_full_te_3caba4` ON `index` (`full_text`) WITH PARSER ngram; +CREATE SPATIAL INDEX `idx_index_geometr_0b4dfb` ON `index` (`geometry`);""", ) async def test_index_unsafe(self): From b0c4f8a6c7c9881768f3bfbe9fedf395e9df5555 Mon Sep 17 00:00:00 2001 From: Waket Zheng Date: Sun, 12 Jan 2025 16:30:25 +0800 Subject: [PATCH 7/7] Only change get_sql --- tortoise/backends/mysql/executor.py | 2 +- tortoise/indexes.py | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/tortoise/backends/mysql/executor.py b/tortoise/backends/mysql/executor.py index 0ab40a19c..6a19be073 100644 --- a/tortoise/backends/mysql/executor.py +++ b/tortoise/backends/mysql/executor.py @@ -1,6 +1,6 @@ import enum -from pypika_tortoise import functions, SqlContext +from pypika_tortoise import SqlContext, functions from pypika_tortoise.enums import SqlTypes from pypika_tortoise.functions import Cast, Coalesce from pypika_tortoise.terms import BasicCriterion, Criterion diff --git a/tortoise/indexes.py b/tortoise/indexes.py index 82a3cda0c..c503fb8bb 100644 --- a/tortoise/indexes.py +++ b/tortoise/indexes.py @@ -15,11 +15,6 @@ class Index: "CREATE{index_type}INDEX {exists}{index_name} ON {table_name} ({fields}){extra};" ) - class _db_mysql: - INDEX_CREATE_TEMPLATE = ( - "CREATE{index_type}INDEX {index_name} ON {table_name} ({fields}){extra};" - ) - def __init__( self, *expressions: Term, @@ -55,12 +50,6 @@ def __repr__(self) -> str: argument += f", {name=}" return self.__class__.__name__ + "(" + argument + ")" - def get_for_dialect(self, dialect: str, key: str) -> Any: - value = getattr(self, key, "") - if dialect_special := getattr(self, f"_db_{dialect.lower()}", None): - return getattr(dialect_special, key, value) - return value - def get_sql( self, schema_generator: "BaseSchemaGenerator", model: "Type[Model]", safe: bool ) -> str: @@ -70,9 +59,9 @@ def get_sql( ctx = schema_generator.client.query_class.SQL_CONTEXT expressions = [f"({expression.get_sql(ctx)})" for expression in self.expressions] fields = ", ".join(expressions) - template = self.get_for_dialect(schema_generator.DIALECT, "INDEX_CREATE_TEMPLATE") - return template.format( - exists="IF NOT EXISTS " if safe else "", + exists = "IF NOT EXISTS " if safe and schema_generator.DIALECT != "mysql" else "" + return self.INDEX_CREATE_TEMPLATE.format( + exists=exists, index_name=schema_generator.quote(self.index_name(schema_generator, model)), index_type=f" {self.INDEX_TYPE} " if self.INDEX_TYPE else " ", table_name=schema_generator.quote(model._meta.db_table),