Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/postgres/search.py: 38%
194 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import psycopg2
3from django.db.models import (
4 CharField,
5 Expression,
6 Field,
7 FloatField,
8 Func,
9 Lookup,
10 TextField,
11 Value,
12)
13from django.db.models.expressions import CombinedExpression
14from django.db.models.functions import Cast, Coalesce
17class SearchVectorExact(Lookup):
18 lookup_name = "exact"
20 def process_rhs(self, qn, connection):
21 if not isinstance(self.rhs, (SearchQuery, CombinedSearchQuery)):
22 config = getattr(self.lhs, "config", None)
23 self.rhs = SearchQuery(self.rhs, config=config)
24 rhs, rhs_params = super().process_rhs(qn, connection)
25 return rhs, rhs_params
27 def as_sql(self, qn, connection):
28 lhs, lhs_params = self.process_lhs(qn, connection)
29 rhs, rhs_params = self.process_rhs(qn, connection)
30 params = lhs_params + rhs_params
31 return "%s @@ %s" % (lhs, rhs), params
34class SearchVectorField(Field):
35 def db_type(self, connection):
36 return "tsvector"
39class SearchQueryField(Field):
40 def db_type(self, connection):
41 return "tsquery"
44class SearchConfig(Expression):
45 def __init__(self, config):
46 super().__init__()
47 if not hasattr(config, "resolve_expression"):
48 config = Value(config)
49 self.config = config
51 @classmethod
52 def from_parameter(cls, config):
53 if config is None or isinstance(config, cls):
54 return config
55 return cls(config)
57 def get_source_expressions(self):
58 return [self.config]
60 def set_source_expressions(self, exprs):
61 (self.config,) = exprs
63 def as_sql(self, compiler, connection):
64 sql, params = compiler.compile(self.config)
65 return "%s::regconfig" % sql, params
68class SearchVectorCombinable:
69 ADD = "||"
71 def _combine(self, other, connector, reversed):
72 if not isinstance(other, SearchVectorCombinable):
73 raise TypeError(
74 "SearchVector can only be combined with other SearchVector "
75 "instances, got %s." % type(other).__name__
76 )
77 if reversed:
78 return CombinedSearchVector(other, connector, self, self.config)
79 return CombinedSearchVector(self, connector, other, self.config)
82class SearchVector(SearchVectorCombinable, Func):
83 function = "to_tsvector"
84 arg_joiner = " || ' ' || "
85 output_field = SearchVectorField()
87 def __init__(self, *expressions, config=None, weight=None):
88 super().__init__(*expressions)
89 self.config = SearchConfig.from_parameter(config)
90 if weight is not None and not hasattr(weight, "resolve_expression"):
91 weight = Value(weight)
92 self.weight = weight
94 def resolve_expression(
95 self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
96 ):
97 resolved = super().resolve_expression(
98 query, allow_joins, reuse, summarize, for_save
99 )
100 if self.config:
101 resolved.config = self.config.resolve_expression(
102 query, allow_joins, reuse, summarize, for_save
103 )
104 return resolved
106 def as_sql(self, compiler, connection, function=None, template=None):
107 clone = self.copy()
108 clone.set_source_expressions(
109 [
110 Coalesce(
111 expression
112 if isinstance(expression.output_field, (CharField, TextField))
113 else Cast(expression, TextField()),
114 Value(""),
115 )
116 for expression in clone.get_source_expressions()
117 ]
118 )
119 config_sql = None
120 config_params = []
121 if template is None:
122 if clone.config:
123 config_sql, config_params = compiler.compile(clone.config)
124 template = "%(function)s(%(config)s, %(expressions)s)"
125 else:
126 template = clone.template
127 sql, params = super(SearchVector, clone).as_sql(
128 compiler,
129 connection,
130 function=function,
131 template=template,
132 config=config_sql,
133 )
134 extra_params = []
135 if clone.weight:
136 weight_sql, extra_params = compiler.compile(clone.weight)
137 sql = "setweight({}, {})".format(sql, weight_sql)
138 return sql, config_params + params + extra_params
141class CombinedSearchVector(SearchVectorCombinable, CombinedExpression):
142 def __init__(self, lhs, connector, rhs, config, output_field=None):
143 self.config = config
144 super().__init__(lhs, connector, rhs, output_field)
147class SearchQueryCombinable:
148 BITAND = "&&"
149 BITOR = "||"
151 def _combine(self, other, connector, reversed):
152 if not isinstance(other, SearchQueryCombinable):
153 raise TypeError(
154 "SearchQuery can only be combined with other SearchQuery "
155 "instances, got %s." % type(other).__name__
156 )
157 if reversed:
158 return CombinedSearchQuery(other, connector, self, self.config)
159 return CombinedSearchQuery(self, connector, other, self.config)
161 # On Combinable, these are not implemented to reduce confusion with Q. In
162 # this case we are actually (ab)using them to do logical combination so
163 # it's consistent with other usage in Django.
164 def __or__(self, other):
165 return self._combine(other, self.BITOR, False)
167 def __ror__(self, other):
168 return self._combine(other, self.BITOR, True)
170 def __and__(self, other):
171 return self._combine(other, self.BITAND, False)
173 def __rand__(self, other):
174 return self._combine(other, self.BITAND, True)
177class SearchQuery(SearchQueryCombinable, Func):
178 output_field = SearchQueryField()
179 SEARCH_TYPES = {
180 "plain": "plainto_tsquery",
181 "phrase": "phraseto_tsquery",
182 "raw": "to_tsquery",
183 "websearch": "websearch_to_tsquery",
184 }
186 def __init__(
187 self,
188 value,
189 output_field=None,
190 *,
191 config=None,
192 invert=False,
193 search_type="plain",
194 ):
195 self.function = self.SEARCH_TYPES.get(search_type)
196 if self.function is None:
197 raise ValueError("Unknown search_type argument '%s'." % search_type)
198 if not hasattr(value, "resolve_expression"):
199 value = Value(value)
200 expressions = (value,)
201 self.config = SearchConfig.from_parameter(config)
202 if self.config is not None:
203 expressions = (self.config,) + expressions
204 self.invert = invert
205 super().__init__(*expressions, output_field=output_field)
207 def as_sql(self, compiler, connection, function=None, template=None):
208 sql, params = super().as_sql(compiler, connection, function, template)
209 if self.invert:
210 sql = "!!(%s)" % sql
211 return sql, params
213 def __invert__(self):
214 clone = self.copy()
215 clone.invert = not self.invert
216 return clone
218 def __str__(self):
219 result = super().__str__()
220 return ("~%s" % result) if self.invert else result
223class CombinedSearchQuery(SearchQueryCombinable, CombinedExpression):
224 def __init__(self, lhs, connector, rhs, config, output_field=None):
225 self.config = config
226 super().__init__(lhs, connector, rhs, output_field)
228 def __str__(self):
229 return "(%s)" % super().__str__()
232class SearchRank(Func):
233 function = "ts_rank"
234 output_field = FloatField()
236 def __init__(
237 self,
238 vector,
239 query,
240 weights=None,
241 normalization=None,
242 cover_density=False,
243 ):
244 if not hasattr(vector, "resolve_expression"):
245 vector = SearchVector(vector)
246 if not hasattr(query, "resolve_expression"):
247 query = SearchQuery(query)
248 expressions = (vector, query)
249 if weights is not None:
250 if not hasattr(weights, "resolve_expression"):
251 weights = Value(weights)
252 expressions = (weights,) + expressions
253 if normalization is not None:
254 if not hasattr(normalization, "resolve_expression"):
255 normalization = Value(normalization)
256 expressions += (normalization,)
257 if cover_density:
258 self.function = "ts_rank_cd"
259 super().__init__(*expressions)
262class SearchHeadline(Func):
263 function = "ts_headline"
264 template = "%(function)s(%(expressions)s%(options)s)"
265 output_field = TextField()
267 def __init__(
268 self,
269 expression,
270 query,
271 *,
272 config=None,
273 start_sel=None,
274 stop_sel=None,
275 max_words=None,
276 min_words=None,
277 short_word=None,
278 highlight_all=None,
279 max_fragments=None,
280 fragment_delimiter=None,
281 ):
282 if not hasattr(query, "resolve_expression"):
283 query = SearchQuery(query)
284 options = {
285 "StartSel": start_sel,
286 "StopSel": stop_sel,
287 "MaxWords": max_words,
288 "MinWords": min_words,
289 "ShortWord": short_word,
290 "HighlightAll": highlight_all,
291 "MaxFragments": max_fragments,
292 "FragmentDelimiter": fragment_delimiter,
293 }
294 self.options = {
295 option: value for option, value in options.items() if value is not None
296 }
297 expressions = (expression, query)
298 if config is not None:
299 config = SearchConfig.from_parameter(config)
300 expressions = (config,) + expressions
301 super().__init__(*expressions)
303 def as_sql(self, compiler, connection, function=None, template=None):
304 options_sql = ""
305 options_params = []
306 if self.options:
307 # getquoted() returns a quoted bytestring of the adapted value.
308 options_params.append(
309 ", ".join(
310 "%s=%s"
311 % (
312 option,
313 psycopg2.extensions.adapt(value).getquoted().decode(),
314 )
315 for option, value in self.options.items()
316 )
317 )
318 options_sql = ", %s"
319 sql, params = super().as_sql(
320 compiler,
321 connection,
322 function=function,
323 template=template,
324 options=options_sql,
325 )
326 return sql, params + options_params
329SearchVectorField.register_lookup(SearchVectorExact)
332class TrigramBase(Func):
333 output_field = FloatField()
335 def __init__(self, expression, string, **extra):
336 if not hasattr(string, "resolve_expression"):
337 string = Value(string)
338 super().__init__(expression, string, **extra)
341class TrigramWordBase(Func):
342 output_field = FloatField()
344 def __init__(self, string, expression, **extra):
345 if not hasattr(string, "resolve_expression"):
346 string = Value(string)
347 super().__init__(string, expression, **extra)
350class TrigramSimilarity(TrigramBase):
351 function = "SIMILARITY"
354class TrigramDistance(TrigramBase):
355 function = ""
356 arg_joiner = " <-> "
359class TrigramWordDistance(TrigramWordBase):
360 function = ""
361 arg_joiner = " <<-> "
364class TrigramWordSimilarity(TrigramWordBase):
365 function = "WORD_SIMILARITY"