Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions tests/fields/test_text.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from tests import testmodels
from tortoise.exceptions import ConfigurationError, IntegrityError
from tortoise.exceptions import IntegrityError
from tortoise.fields import TextField


Expand Down Expand Up @@ -36,20 +36,13 @@ async def test_values_list(db):
assert values == "baa"


def test_unique_fail():
msg = "TextField can't be indexed, consider CharField"
with pytest.raises(ConfigurationError, match=msg):
with pytest.warns(
DeprecationWarning, match="`index` is deprecated, please use `db_index` instead"
):
TextField(index=True)
with pytest.raises(ConfigurationError, match=msg):
TextField(db_index=True)


def test_index_fail():
with pytest.raises(ConfigurationError, match="can't be indexed, consider CharField"):
TextField(index=True)
def test_index_options_are_deferred_to_the_database_dialect():
assert TextField(unique=True).unique is True
assert TextField(db_index=True).index is True
with pytest.warns(
DeprecationWarning, match="`index` is deprecated, please use `db_index` instead"
):
assert TextField(index=True).index is True


def test_pk_deprecated():
Expand Down
38 changes: 38 additions & 0 deletions tests/migrations/test_schema_editor_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from tortoise import fields
from tortoise.contrib.postgres.fields import TSVectorField
from tortoise.contrib.postgres.indexes import GinIndex
from tortoise.exceptions import ConfigurationError
from tortoise.indexes import Index
from tortoise.migrations.schema_editor.base import BaseSchemaEditor
from tortoise.migrations.schema_editor.base_postgres import BasePostgresSchemaEditor
Expand Down Expand Up @@ -53,6 +54,43 @@ async def test_create_model_generates_table_sql() -> None:
assert "PRIMARY KEY" in sql


@pytest.mark.asyncio
async def test_postgres_create_model_supports_text_field_indexes() -> None:
class IndexedTextWidget(Model):
id = fields.IntField(primary_key=True)
unique_text = fields.TextField(unique=True)
indexed_text = fields.TextField(db_index=True)

class Meta:
table = "indexed_text_widget"
app = "models"

client = FakeClient("postgres", inline_comment=False)
editor = BasePostgresSchemaEditor(client)

await editor.create_model(IndexedTextWidget)

assert '"unique_text" TEXT NOT NULL UNIQUE' in client.executed[0]
assert 'ON "indexed_text_widget" ("indexed_text")' in client.executed[0]


@pytest.mark.asyncio
async def test_non_postgres_create_model_rejects_text_field_indexes() -> None:
class IndexedTextWidget(Model):
id = fields.IntField(primary_key=True)
indexed_text = fields.TextField(db_index=True)

class Meta:
table = "indexed_text_widget"
app = "models"

client = FakeClient("sql")
editor = TestSchemaEditor(client)

with pytest.raises(ConfigurationError, match="TextField can't be indexed for sql"):
await editor.create_model(IndexedTextWidget)


@pytest.mark.asyncio
async def test_add_field_generates_add_column_sql() -> None:
client = FakeClient("sql")
Expand Down
6 changes: 6 additions & 0 deletions tests/schema/models_text_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from tortoise import Model, fields


class TextIndex(Model):
unique_text = fields.TextField(unique=True)
indexed_text = fields.TextField(db_index=True)
36 changes: 36 additions & 0 deletions tests/schema/test_generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ async def test_noid():
await _teardown_tortoise()


@pytest.mark.asyncio
async def test_sqlite_rejects_text_field_indexes():
await _reset_tortoise()
try:
with pytest.raises(ConfigurationError, match="TextField can't be indexed for sqlite"):
await _init_for_sqlite("tests.schema.models_text_index")
finally:
await _teardown_tortoise()


@pytest.mark.asyncio
async def test_minrelation():
await _reset_tortoise()
Expand Down Expand Up @@ -1063,6 +1073,19 @@ async def test_asyncpg_noid():
await _teardown_tortoise()


@pytest.mark.asyncio
async def test_asyncpg_text_field_indexes():
await _reset_tortoise()
try:
await _init_for_asyncpg("tests.schema.models_text_index")
sql = get_schema_sql(connections.get("default"), safe=False)
assert '"unique_text" TEXT NOT NULL UNIQUE' in sql
assert 'CREATE INDEX "idx_textindex_indexed_' in sql
assert 'ON "textindex" ("indexed_text")' in sql
finally:
await _teardown_tortoise()


@pytest.mark.asyncio
async def test_asyncpg_table_and_row_comment_generation():
await _reset_tortoise()
Expand Down Expand Up @@ -1569,6 +1592,19 @@ async def test_psycopg_noid():
await _teardown_tortoise()


@pytest.mark.asyncio
async def test_psycopg_text_field_indexes():
await _reset_tortoise()
try:
await _init_for_psycopg("tests.schema.models_text_index")
sql = get_schema_sql(connections.get("default"), safe=False)
assert '"unique_text" TEXT NOT NULL UNIQUE' in sql
assert 'CREATE INDEX "idx_textindex_indexed_' in sql
assert 'ON "textindex" ("indexed_text")' in sql
finally:
await _teardown_tortoise()


@pytest.mark.asyncio
async def test_psycopg_table_and_row_comment_generation():
await _reset_tortoise()
Expand Down
1 change: 1 addition & 0 deletions tortoise/backends/base/schema_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ def _get_table_sql(self, model: type[Model], safe: bool = True) -> dict:
models_tables = [model._meta.db_table for model in models_to_create]
for field_name, column_name in model._meta.fields_db_projection.items():
field_object = model._meta.fields_map[field_name]
field_object._validate_indexable(self.DIALECT)
comment = self._get_field_comment(field_object, qualified_table_name, column_name)
default = self._get_field_default(field_object, table_name, column_name, model)

Expand Down
17 changes: 16 additions & 1 deletion tortoise/fields/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def __init__(
raise ConfigurationError(
f"{self.__class__.__name__} can't set both db_index and index"
)
if not self.indexable and (unique or db_index):
if not self._is_indexable() and (unique or db_index):
raise ConfigurationError(f"{self.__class__.__name__} can't be indexed")
if (pk := kwargs.pop("pk", None)) is not None:
if primary_key is None:
Expand Down Expand Up @@ -290,6 +290,21 @@ def __init__(
self.model: type[Model] = model # type: ignore
self.reference: Field | None = None

def _is_indexable(self, dialect: str | None = None) -> bool:
if dialect is not None:
return bool(self.get_for_dialect(dialect, "indexable"))
if self.indexable:
return True
return any(
bool(getattr(getattr(self, name), "indexable", False))
for name in dir(self)
if name.startswith("_db_")
)

def _validate_indexable(self, dialect: str) -> None:
if not self.pk and (self.unique or self.index) and not self._is_indexable(dialect):
raise ConfigurationError(f"{self.__class__.__name__} can't be indexed for {dialect}")

def __copy__(self) -> Field:
cls = self.__class__
result = cls.__new__(cls)
Expand Down
23 changes: 7 additions & 16 deletions tortoise/fields/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ def SQL_TYPE(self) -> str:
class TextField(Field[str], str): # type: ignore
"""
Large Text field.

PostgreSQL supports ``unique=True`` and ``db_index=True``. Other backends reject these
options when generating or migrating the database schema.
"""

indexable = False
Expand All @@ -268,7 +271,7 @@ def __init__(
self,
primary_key: bool | None = None,
unique: bool = False,
db_index: bool = False,
db_index: bool | None = None,
**kwargs: Any,
) -> None:
if primary_key or kwargs.get("pk"):
Expand All @@ -277,22 +280,10 @@ def __init__(
DeprecationWarning,
stacklevel=2,
)
if unique:
raise ConfigurationError(
"TextField doesn't support unique indexes, consider CharField or another strategy"
)
if (index := kwargs.pop("index", None)) is not None:
warnings.warn(
"`index` is deprecated, please use `db_index` instead",
DeprecationWarning,
stacklevel=2,
)
if index or db_index:
raise ConfigurationError("TextField can't be indexed, consider CharField")
elif db_index:
raise ConfigurationError("TextField can't be indexed, consider CharField")
super().__init__(primary_key=primary_key, unique=unique, db_index=db_index, **kwargs)

super().__init__(primary_key=primary_key, **kwargs)
class _db_postgres:
indexable = True

class _db_mysql:
SQL_TYPE = "LONGTEXT"
Expand Down
3 changes: 3 additions & 0 deletions tortoise/migrations/schema_editor/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ def _get_model_sql_data(self, model: type[Model]) -> ModelSqlData:

for field_name, db_field in model._meta.fields_db_projection.items():
field_object = model._meta.fields_map[field_name]
field_object._validate_indexable(self.DIALECT)
comment = (
self._get_column_comment_sql(
table=qualified_table_name,
Expand Down Expand Up @@ -500,6 +501,7 @@ async def delete_model(self, model: type[Model]) -> None:

async def add_field(self, model: type[Model], field_name: str) -> None:
field = model._meta.fields_map[field_name]
field._validate_indexable(self.DIALECT)
if isinstance(field, ManyToManyFieldInstance):
table_string = self._get_m2m_table_definition(model, field)
if table_string:
Expand Down Expand Up @@ -614,6 +616,7 @@ async def _alter_generated_field(
return False

async def _alter_field(self, model: type[Model], old_field: Field, new_field: Field) -> None:
new_field._validate_indexable(self.DIALECT)
actions: list[str] = []
old_db_field = old_field.source_field or old_field.model_field_name
new_db_field = new_field.source_field or new_field.model_field_name
Expand Down
Loading