summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Turk <dev@jamesturk.net>2019-12-13 15:10:33 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-12-16 14:59:59 +0100
commitff00a053478fee06bdfb4206c6d4e079e98640ff (patch)
treef9fd5de71875e2b19a3ff69820ead99d4ed97e83
parent972d93a95ec8b37fab84400417070b7439414967 (diff)
Fixed #31088 -- Added support for websearch searching in SearchQuery.
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/postgres/search.py1
-rw-r--r--django/db/backends/postgresql/features.py5
-rw-r--r--docs/ref/contrib/postgres/search.txt12
-rw-r--r--docs/releases/3.1.txt3
-rw-r--r--tests/postgres_tests/test_search.py39
6 files changed, 59 insertions, 2 deletions
diff --git a/AUTHORS b/AUTHORS
index 70c18d8e18..13010b754d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -390,6 +390,7 @@ answer newbie questions, and generally made Django that much better:
James Murty
James Tauber <jtauber@jtauber.com>
James Timmins <jameshtimmins@gmail.com>
+ James Turk <dev@jamesturk.net>
James Wheare <django@sparemint.com>
Jannis Leidel <jannis@leidel.info>
Janos Guljas
diff --git a/django/contrib/postgres/search.py b/django/contrib/postgres/search.py
index 08d92e3514..c3b940d436 100644
--- a/django/contrib/postgres/search.py
+++ b/django/contrib/postgres/search.py
@@ -134,6 +134,7 @@ class SearchQuery(SearchQueryCombinable, Value):
'plain': 'plainto_tsquery',
'phrase': 'phraseto_tsquery',
'raw': 'to_tsquery',
+ 'websearch': 'websearch_to_tsquery',
}
def __init__(self, value, output_field=None, *, config=None, invert=False, search_type='plain'):
diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index 58b16d2b48..fad87081b0 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -65,10 +65,15 @@ class DatabaseFeatures(BaseDatabaseFeatures):
return self.connection.pg_version >= 100000
@cached_property
+ def is_postgresql_11(self):
+ return self.connection.pg_version >= 110000
+
+ @cached_property
def is_postgresql_12(self):
return self.connection.pg_version >= 120000
has_bloom_index = property(operator.attrgetter('is_postgresql_9_6'))
has_brin_autosummarize = property(operator.attrgetter('is_postgresql_10'))
has_phraseto_tsquery = property(operator.attrgetter('is_postgresql_9_6'))
+ has_websearch_to_tsquery = property(operator.attrgetter('is_postgresql_11'))
supports_table_partitions = property(operator.attrgetter('is_postgresql_10'))
diff --git a/docs/ref/contrib/postgres/search.txt b/docs/ref/contrib/postgres/search.txt
index c78183cd01..813a3db57a 100644
--- a/docs/ref/contrib/postgres/search.txt
+++ b/docs/ref/contrib/postgres/search.txt
@@ -80,8 +80,11 @@ looks for matches for all of the resulting terms.
If ``search_type`` is ``'plain'``, which is the default, the terms are treated
as separate keywords. If ``search_type`` is ``'phrase'``, the terms are treated
as a single phrase. If ``search_type`` is ``'raw'``, then you can provide a
-formatted search query with terms and operators. Read PostgreSQL's `Full Text
-Search docs`_ to learn about differences and syntax. Examples:
+formatted search query with terms and operators. If ``search_type`` is
+``'websearch'``, then you can provide a formatted search query, similar to the
+one used by web search engines. ``'websearch'`` requires PostgreSQL ≥ 11. Read
+PostgreSQL's `Full Text Search docs`_ to learn about differences and syntax.
+Examples:
.. _Full Text Search docs: https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES
@@ -91,6 +94,7 @@ Search docs`_ to learn about differences and syntax. Examples:
>>> SearchQuery('red tomato', search_type='phrase') # a phrase
>>> SearchQuery('tomato red', search_type='phrase') # a different phrase
>>> SearchQuery("'tomato' & ('red' | 'green')", search_type='raw') # boolean operators
+ >>> SearchQuery("'tomato' ('red' OR 'green')", search_type='websearch') # websearch operators
``SearchQuery`` terms can be combined logically to provide more flexibility::
@@ -102,6 +106,10 @@ Search docs`_ to learn about differences and syntax. Examples:
See :ref:`postgresql-fts-search-configuration` for an explanation of the
``config`` parameter.
+.. versionchanged:: 3.1
+
+ Support for ``'websearch'`` search type was added.
+
``SearchRank``
==============
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index b98d770ac5..136f2900b8 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -97,6 +97,9 @@ Minor features
:class:`~django.db.models.SmallIntegerField`, and
:class:`~django.db.models.DecimalField`.
+* :class:`~django.contrib.postgres.search.SearchQuery` now supports
+ ``'websearch'`` search type on PostgreSQL 11+.
+
:mod:`django.contrib.redirects`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/postgres_tests/test_search.py b/tests/postgres_tests/test_search.py
index f5111ce8d3..07b1f9e9b4 100644
--- a/tests/postgres_tests/test_search.py
+++ b/tests/postgres_tests/test_search.py
@@ -202,6 +202,45 @@ class MultipleFieldsTest(GrailTestData, PostgreSQLTestCase):
)
self.assertSequenceEqual(searched, [self.french])
+ @skipUnlessDBFeature('has_websearch_to_tsquery')
+ def test_web_search(self):
+ line_qs = Line.objects.annotate(search=SearchVector('dialogue'))
+ searched = line_qs.filter(
+ search=SearchQuery(
+ '"burned body" "split kneecaps"',
+ search_type='websearch',
+ ),
+ )
+ self.assertSequenceEqual(searched, [])
+ searched = line_qs.filter(
+ search=SearchQuery(
+ '"body burned" "kneecaps split" -"nostrils"',
+ search_type='websearch',
+ ),
+ )
+ self.assertSequenceEqual(searched, [self.verse1])
+ searched = line_qs.filter(
+ search=SearchQuery(
+ '"Sir Robin" ("kneecaps" OR "Camelot")',
+ search_type='websearch',
+ ),
+ )
+ self.assertSequenceEqual(searched, [self.verse0, self.verse1])
+
+ @skipUnlessDBFeature('has_websearch_to_tsquery')
+ def test_web_search_with_config(self):
+ line_qs = Line.objects.annotate(
+ search=SearchVector('scene__setting', 'dialogue', config='french'),
+ )
+ searched = line_qs.filter(
+ search=SearchQuery('cadeau -beau', search_type='websearch', config='french'),
+ )
+ self.assertSequenceEqual(searched, [])
+ searched = line_qs.filter(
+ search=SearchQuery('beau cadeau', search_type='websearch', config='french'),
+ )
+ self.assertSequenceEqual(searched, [self.french])
+
def test_bad_search_type(self):
with self.assertRaisesMessage(ValueError, "Unknown search_type argument 'foo'."):
SearchQuery('kneecaps', search_type='foo')