Django MySql全文搜索有效,但不适用于测试

Posted

技术标签:

【中文标题】Django MySql全文搜索有效,但不适用于测试【英文标题】:Django MySql Fulltext search works but not on tests 【发布时间】:2020-08-12 15:38:25 【问题描述】:

我使用SO question 在 Django 应用程序中的 mysql 数据库上启用全文搜索。

# models.py

class CaseSnapshot(BaseModel):
    text = models.TextField(blank=True)


class Search(models.Lookup):
    lookup_name = "search"

    def as_mysql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = lhs_params + rhs_params
        return f"MATCH (%s) AGAINST (%s IN BOOLEAN MODE)" % (lhs, rhs), params


models.TextField.register_lookup(Search)

因为django不支持mysql上的全文搜索,所以要添加我们的索引,所以修改生成的迁移文件:

# Generated by Django 2.2 on 2020-04-28 03:41

from django.db import migrations, models

# Table details
table_name = "by_api_casesnapshot"
field_name = "text"
index_name = f"table_name_field_name_index"


class Migration(migrations.Migration):

    dependencies = [("by_api", "0033_add_tag_color")]

    operations = [
        migrations.CreateModel(...), # As auto-generated
        migrations.RunSQL(
            f"CREATE FULLTEXT INDEX index_name ON table_name (field_name)",
            f"DROP INDEX index_name ON table_name",
        ),
    ]

检查工作

# dbshell

mysql> SHOW INDEX FROM by_api_casesnapshot;

+---------------------+--------------------------------+-------------+-----+------------+-----+
| Table               | Key_name                       | Column_name | ... | Index_type | ... |
+---------------------+--------------------------------+-------------+-----+------------+-----+
| by_api_casesnapshot | PRIMARY                        | id          | ... | BTREE      | ... |
| by_api_casesnapshot | by_api_casesnapshot_text_index | text        | ... | FULLTEXT   | ... |
+---------------------+--------------------------------+-------------+-----+------------+-----+
#shell 
>>> snap = CaseSnapshot.objects.create(text="XXX")

# Regular query first
>>> CaseSnapshot.objects.filter(text__contains="X") 
<QuerySet [<CaseSnapshot pk=1>]>

# Now test custom search lookup
>>> CaseSnapshot.objects.filter(text__search="XXX")
<QuerySet [<CaseSnapshot pk=1>]>

>>> CaseSnapshot.objects.filter(text__search="X*")
<QuerySet [<CaseSnapshot pk=1>]>

>>> CaseSnapshot.objects.filter(text__search="X*").query.__str__()

'SELECT `by_api_casesnapshot`.`id`, `by_api_casesnapshot`.`text` FROM `by_api_casesnapshot` WHERE MATCH (`by_api_casesnapshot`.`text`) AGAINST (X* IN BOOLEAN M
ODE)'

到目前为止,一切都完全按照预期工作。

问题

这就是失败的地方 - 所有使用__search 查找的查询在测试运行期间返回一个查询集空集,即使生成的查询是相同的。

测试

from django.test import TestCase
from by_api.models import CaseSnapshot


class TestModels(TestCase):
    def test_search(self):
        CaseSnapshot.objects.create(text="XXX")
        rv1 = CaseSnapshot.objects.filter(text__contains="XXX")
        # Passes
        self.assertEqual(rv1.count(), 1)

        rv2 = CaseSnapshot.objects.filter(text__search="XXX")
        # Fails - count i
        self.assertEqual(rv2.count(), 1)


我尝试在测试运行中使用 pdb 对其进行调试,并注意生成的查询与上面在 shell 环境中生成的查询相同:

>>> rv2.query.__str__())
'SELECT `by_api_casesnapshot`.`id`, `by_api_casesnapshot`.`text` FROM `by_api_casesnapshot` WHERE MATCH (`by_api_casesnapshot`.`text`) AGAINST (XXX IN BOOLEAN MODE)'

问题

为什么在测试期间使用 __search 查找会产生空查询集?

我尝试过的

查看mysql stop words causing empty queries 确保 test db 和 shell db 具有相同的值 尝试使用游标执行生成的查询 - 也是空的

更新

我刚刚注意到如果我使用 unittest.TestCase 而不是 django.test.TestCase 测试通过 - 这可能与 django 的测试用例如何包装数据库事务有关吗?

【问题讨论】:

【参考方案1】:

来自 django-mysql 库的 Adam Chainz 的回答:

在提交之前,InnoDB 不会更改全文索引。您可以使用 TransactionTestCase 来解决这个问题。

我将测试类更改为使用TransactionTestCase,现在测试通过了。

【讨论】:

以上是关于Django MySql全文搜索有效,但不适用于测试的主要内容,如果未能解决你的问题,请参考以下文章

在视图中使用全文搜索 + GIN (Django 1.11 )

Django MySQL 全文搜索

使用邮递员的 GET 请求有效,但不适用于 python (HTTPSConnectionPool)

查询适用于 phpMyAdmin,但不适用于 Java [重复]

mysqldump 不适用于全文索引?

Python Django:tzinfo 不适用于数据库插入。但是为啥 .now(local_tz) 有效?