I think you were not using completely well the full-text search Django module.
The main issue I can see in your code are:
- updating the search vector field without filtering your object
- executing your search query on an annotated
SearchVector
using an icontains
instead of using the SearchVectorField
with your GinIndex
I updated a bit your models code:
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField, SearchVector
from django.db import models
from django.db.models import F
class ProfileUser(models.Model):
name = JSONField()
search_vector = SearchVectorField(null=True)
class Meta:
indexes = [GinIndex(fields=["search_vector"], name="user_full_name_gin_idx")]
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
ProfileUser.objects.annotate(search_vector_name=SearchVector("name")).filter(
id=self.id
).update(search_vector=F("search_vector_name"))
As you can see I added an annotate and a filter in save
method to update only the search vector fields of your model (You can find in another answer of mine another example of this usage)
Here you can see the code I used in the python shell to create a new ProfileUser
.
You can see the two SQL query executed in the save
method:
>>> from users.models import ProfileUser
>>> ProfileUser.objects.create(name=[{'name': 'SomeUser', 'lang': 'en'}])
INSERT INTO "users_profileuser" ("name", "search_vector")
VALUES ('[{"name": "SomeUser", "lang": "en"}]', NULL) RETURNING "users_profileuser"."id"
UPDATE "users_profileuser"
SET "search_vector" = to_tsvector(COALESCE(("users_profileuser"."name")::text, ''))
WHERE "users_profileuser"."id" = 1
And below there the code I executed in the python shell to search the ProfileUser
with the SearchVectorField
using the GINindex
of the model.
You can see the Index Scan
on the index:
>>> from django.contrib.postgres.search import SearchQuery
>>> ProfileUser.objects.filter(search_vector=SearchQuery('someuser')).explain()
EXPLAIN
SELECT "users_profileuser"."id",
"users_profileuser"."name",
"users_profileuser"."search_vector"
FROM "users_profileuser"
WHERE "users_profileuser"."search_vector" @@ (plainto_tsquery('someuser')) = true
"Bitmap Heap Scan on users_profileuser (cost=12.28..21.74 rows=4 width=68)
Recheck Cond: (search_vector @@ plainto_tsquery('someuser'::text))
-> Bitmap Index Scan on user_full_name_gin_idx (cost=0.00..12.28 rows=4 width=0)
Index Cond: (search_vector @@ plainto_tsquery('someuser'::text))"
If you want to know more about Full-text Search with Django and PostgreSQL you can read the official documentation about the full-text search.
If you are interested in external article about that here it's the one I wrote:
Full-Text Search in Django with PostgreSQL