Skip to content

Commit

Permalink
Fix index creation in Tortoise.generate_schemas() for MySQL and Postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
henadzit committed Jan 10, 2025
1 parent 7afc90e commit a833893
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 66 deletions.
20 changes: 10 additions & 10 deletions tests/schema/test_generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,10 +724,10 @@ async def test_index_safe(self):
"""CREATE TABLE IF NOT EXISTS `index` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`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`);""",
`geometry` GEOMETRY NOT NULL,
FULLTEXT KEY `idx_index_full_te_3caba4` (`full_text`) WITH PARSER ngram,
SPATIAL KEY `idx_index_geometr_0b4dfb` (`geometry`)
) CHARACTER SET utf8mb4;""",
)

async def test_index_unsafe(self):
Expand All @@ -738,10 +738,10 @@ async def test_index_unsafe(self):
"""CREATE TABLE `index` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`full_text` LONGTEXT NOT NULL,
`geometry` GEOMETRY NOT NULL
) CHARACTER SET utf8mb4;
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`);""",
`geometry` GEOMETRY NOT NULL,
FULLTEXT KEY `idx_index_full_te_3caba4` (`full_text`) WITH PARSER ngram,
SPATIAL KEY `idx_index_geometr_0b4dfb` (`geometry`)
) CHARACTER SET utf8mb4;""",
)

async def test_m2m_no_auto_create(self):
Expand Down Expand Up @@ -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" ("partial") WHERE id = 1;""",
)

async def test_index_safe(self):
Expand All @@ -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" ("partial") WHERE id = 1;""",
)

async def test_m2m_no_auto_create(self):
Expand Down
48 changes: 35 additions & 13 deletions tortoise/backends/base/schema_generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re
from hashlib import sha256
from typing import TYPE_CHECKING, Any, List, Set, Type, Union, cast
from typing import TYPE_CHECKING, Any, List, Optional, Set, Type, Union, cast

from tortoise.exceptions import ConfigurationError
from tortoise.fields import JSONField, TextField, UUIDField
Expand All @@ -23,8 +23,10 @@ class BaseSchemaGenerator:
DIALECT = "sql"
TABLE_CREATE_TEMPLATE = 'CREATE TABLE {exists}"{table_name}" ({fields}){extra}{comment};'
FIELD_TEMPLATE = '"{name}" {type}{nullable}{unique}{primary}{default}{comment}'
INDEX_CREATE_TEMPLATE = 'CREATE INDEX {exists}"{index_name}" ON "{table_name}" ({fields});'
UNIQUE_INDEX_CREATE_TEMPLATE = INDEX_CREATE_TEMPLATE.replace(" INDEX", " UNIQUE INDEX")
INDEX_CREATE_TEMPLATE = (
'CREATE {index_type}INDEX {exists}"{index_name}" ON "{table_name}" ({fields}){extra};'
)
UNIQUE_INDEX_CREATE_TEMPLATE = INDEX_CREATE_TEMPLATE.replace("INDEX", "UNIQUE INDEX")
UNIQUE_CONSTRAINT_CREATE_TEMPLATE = 'CONSTRAINT "{index_name}" UNIQUE ({fields})'
GENERATED_PK_TEMPLATE = '"{field_name}" {generated_sql}{comment}'
FK_TEMPLATE = ' REFERENCES "{table}" ("{field}") ON DELETE {on_delete}{comment}'
Expand Down Expand Up @@ -167,21 +169,33 @@ def _generate_fk_name(
)
return index_name

def _get_index_sql(self, model: "Type[Model]", field_names: List[str], safe: bool) -> str:
def _get_index_sql(
self,
model: "Type[Model]",
field_names: List[str],
safe: bool,
index_name: Optional[str] = None,
index_type: Optional[str] = None,
extra: Optional[str] = None,
) -> str:
return self.INDEX_CREATE_TEMPLATE.format(
exists="IF NOT EXISTS " if safe else "",
index_name=self._generate_index_name("idx", model, field_names),
index_name=index_name or self._generate_index_name("idx", model, field_names),
index_type=f"{index_type} " if index_type else "",
table_name=model._meta.db_table,
fields=", ".join([self.quote(f) for f in field_names]),
extra=f"{extra}" if extra else "",
)

def _get_unique_index_sql(self, exists: str, table_name: str, field_names: List[str]) -> str:
index_name = self._generate_index_name("uidx", table_name, field_names)
return self.UNIQUE_INDEX_CREATE_TEMPLATE.format(
exists=exists,
index_name=index_name,
index_type="",
table_name=table_name,
fields=", ".join([self.quote(f) for f in field_names]),
extra="",
)

def _get_unique_constraint_sql(self, model: "Type[Model]", field_names: List[str]) -> str:
Expand Down Expand Up @@ -324,22 +338,30 @@ def _get_table_sql(self, model: "Type[Model]", safe: bool = True) -> dict:
self._get_unique_constraint_sql(model, unique_together_to_create)
)

# Indexes.
_indexes = [
self._get_index_sql(model, [field_name], safe=safe) for field_name in fields_with_index
]

if model._meta.indexes:
for indexes_list in model._meta.indexes:
if not isinstance(indexes_list, Index):
indexes_to_create = []
for field in indexes_list:
for index in model._meta.indexes:
if not isinstance(index, Index):
fields = []
for field in index:
field_object = model._meta.fields_map[field]
indexes_to_create.append(field_object.source_field or field)
fields.append(field_object.source_field or field)

_indexes.append(self._get_index_sql(model, indexes_to_create, safe=safe))
_indexes.append(self._get_index_sql(model, fields, safe=safe))
else:
_indexes.append(indexes_list.get_sql(self, model, safe))
if index.fields:
fields = [f for f in index.fields]
else:
fields = [f"({expression.get_sql()})" for expression in index.expressions]

_indexes.append(
self._get_index_sql(
model, fields, safe=safe, index_type=index.INDEX_TYPE, extra=index.extra
)
)

field_indexes_sqls = [val for val in list(dict.fromkeys(_indexes)) if val]

Expand Down
23 changes: 22 additions & 1 deletion tortoise/backends/base_postgres/schema_generator.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from typing import TYPE_CHECKING, Any, List
from typing import TYPE_CHECKING, Any, List, Optional, Type

from tortoise.backends.base.schema_generator import BaseSchemaGenerator
from tortoise.converters import encoders
from tortoise.models import Model

if TYPE_CHECKING: # pragma: nocoverage
from .client import BasePostgresClient


class BasePostgresSchemaGenerator(BaseSchemaGenerator):
DIALECT = "postgres"
INDEX_CREATE_TEMPLATE = (
'CREATE INDEX {exists}"{index_name}" ON "{table_name}" {index_type}({fields}){extra};'
)
UNIQUE_INDEX_CREATE_TEMPLATE = INDEX_CREATE_TEMPLATE.replace("INDEX", "UNIQUE INDEX")
TABLE_COMMENT_TEMPLATE = "COMMENT ON TABLE \"{table}\" IS '{comment}';"
COLUMN_COMMENT_TEMPLATE = 'COMMENT ON COLUMN "{table}"."{column}" IS \'{comment}\';'
GENERATED_PK_TEMPLATE = '"{field_name}" {generated_sql}'
Expand Down Expand Up @@ -61,3 +66,19 @@ def _escape_default_value(self, default: Any):
if isinstance(default, bool):
return default
return encoders.get(type(default))(default) # type: ignore

def _get_index_sql(
self,
model: "Type[Model]",
field_names: List[str],
safe: bool,
index_name: Optional[str] = None,
index_type: Optional[str] = None,
extra: Optional[str] = None,
) -> str:
if index_type:
index_type = f"USING {index_type}"

return super()._get_index_sql(
model, field_names, safe, index_name=index_name, index_type=index_type, extra=extra
)
16 changes: 13 additions & 3 deletions tortoise/backends/mssql/schema_generator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, List, Type
from typing import TYPE_CHECKING, Any, List, Optional, Type

from tortoise.backends.base.schema_generator import BaseSchemaGenerator
from tortoise.converters import encoders
Expand Down Expand Up @@ -59,8 +59,18 @@ def _column_default_generator(
def _escape_default_value(self, default: Any):
return encoders.get(type(default))(default) # type: ignore

def _get_index_sql(self, model: "Type[Model]", field_names: List[str], safe: bool) -> str:
return super(MSSQLSchemaGenerator, self)._get_index_sql(model, field_names, False)
def _get_index_sql(
self,
model: "Type[Model]",
field_names: List[str],
safe: bool,
index_name: Optional[str] = None,
index_type: Optional[str] = None,
extra: Optional[str] = None,
) -> str:
return super(MSSQLSchemaGenerator, self)._get_index_sql(
model, field_names, False, index_name=index_name, index_type=index_type, extra=extra
)

def _get_table_sql(self, model: "Type[Model]", safe: bool = True) -> dict:
return super(MSSQLSchemaGenerator, self)._get_table_sql(model, False)
Expand Down
18 changes: 14 additions & 4 deletions tortoise/backends/mysql/schema_generator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, List, Type
from typing import TYPE_CHECKING, Any, List, Optional, Type

from tortoise.backends.base.schema_generator import BaseSchemaGenerator
from tortoise.converters import encoders
Expand All @@ -11,7 +11,7 @@
class MySQLSchemaGenerator(BaseSchemaGenerator):
DIALECT = "mysql"
TABLE_CREATE_TEMPLATE = "CREATE TABLE {exists}`{table_name}` ({fields}){extra}{comment};"
INDEX_CREATE_TEMPLATE = "KEY `{index_name}` ({fields})"
INDEX_CREATE_TEMPLATE = "{index_type}KEY `{index_name}` ({fields}){extra}"
UNIQUE_CONSTRAINT_CREATE_TEMPLATE = "UNIQUE KEY `{index_name}` ({fields})"
UNIQUE_INDEX_CREATE_TEMPLATE = UNIQUE_CONSTRAINT_CREATE_TEMPLATE
FIELD_TEMPLATE = "`{name}` {type}{nullable}{unique}{primary}{comment}{default}"
Expand Down Expand Up @@ -68,9 +68,19 @@ def _column_default_generator(
def _escape_default_value(self, default: Any):
return encoders.get(type(default))(default) # type: ignore

def _get_index_sql(self, model: "Type[Model]", field_names: List[str], safe: bool) -> str:
def _get_index_sql(
self,
model: "Type[Model]",
field_names: List[str],
safe: bool,
index_name: Optional[str] = None,
index_type: Optional[str] = None,
extra: Optional[str] = None,
) -> str:
"""Get index SQLs, but keep them for ourselves"""
index_create_sql = super()._get_index_sql(model, field_names, safe)
index_create_sql = super()._get_index_sql(
model, field_names, safe, index_name=index_name, index_type=index_type, extra=extra
)
self._field_indexes.append(index_create_sql)
return ""

Expand Down
16 changes: 13 additions & 3 deletions tortoise/backends/oracle/schema_generator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, List, Type
from typing import TYPE_CHECKING, Any, List, Optional, Type

from tortoise.backends.base.schema_generator import BaseSchemaGenerator
from tortoise.converters import encoders
Expand Down Expand Up @@ -85,8 +85,18 @@ def _column_default_generator(
def _escape_default_value(self, default: Any):
return encoders.get(type(default))(default) # type: ignore

def _get_index_sql(self, model: "Type[Model]", field_names: List[str], safe: bool) -> str:
return super(OracleSchemaGenerator, self)._get_index_sql(model, field_names, False)
def _get_index_sql(
self,
model: "Type[Model]",
field_names: List[str],
safe: bool,
index_name: Optional[str] = None,
index_type: Optional[str] = None,
extra: Optional[str] = None,
) -> str:
return super(OracleSchemaGenerator, self)._get_index_sql(
model, field_names, False, index_name=index_name, index_type=index_type, extra=extra
)

def _get_table_sql(self, model: "Type[Model]", safe: bool = True) -> dict:
return super(OracleSchemaGenerator, self)._get_table_sql(model, False)
Expand Down
4 changes: 1 addition & 3 deletions tortoise/contrib/postgres/indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@


class PostgreSQLIndex(PartialIndex):
INDEX_CREATE_TEMPLATE = (
"CREATE INDEX {exists}{index_name} ON {table_name} USING{index_type}({fields}){extra};"
)
pass


class BloomIndex(PostgreSQLIndex):
Expand Down
30 changes: 1 addition & 29 deletions tortoise/indexes.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Type
from typing import Any

from pypika_tortoise.terms import Term, ValueWrapper

if TYPE_CHECKING:
from tortoise import Model
from tortoise.backends.base.schema_generator import BaseSchemaGenerator


class Index:
INDEX_TYPE = ""
INDEX_CREATE_TEMPLATE = (
"CREATE{index_type}INDEX {exists}{index_name} ON {table_name} ({fields}){extra};"
)

def __init__(
self,
Expand Down Expand Up @@ -50,27 +43,6 @@ def __repr__(self) -> str:
argument += f", {name=}"
return self.__class__.__name__ + "(" + argument + ")"

def get_sql(
self, schema_generator: "BaseSchemaGenerator", model: "Type[Model]", safe: bool
) -> str:
if self.fields:
fields = ", ".join(schema_generator.quote(f) for f in self.fields)
else:
expressions = [f"({expression.get_sql()})" for expression in self.expressions]
fields = ", ".join(expressions)

return self.INDEX_CREATE_TEMPLATE.format(
exists="IF NOT EXISTS " if safe else "",
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),
fields=fields,
extra=self.extra,
)

def index_name(self, schema_generator: "BaseSchemaGenerator", model: "Type[Model]") -> str:
return self.name or schema_generator._generate_index_name("idx", model, self.fields)

def __hash__(self) -> int:
return hash((tuple(self.fields), self.name, tuple(self.expressions)))

Expand Down

0 comments on commit a833893

Please sign in to comment.