Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
ext.py497 linesDownload Raw Back to postgresql
1# dialects/postgresql/ext.py
2# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8from __future__ import annotations
9
10from typing import Any
11from typing import TYPE_CHECKING
12from typing import TypeVar
13
14from . import types
15from .array import ARRAY
16from ...sql import coercions
17from ...sql import elements
18from ...sql import expression
19from ...sql import functions
20from ...sql import roles
21from ...sql import schema
22from ...sql.schema import ColumnCollectionConstraint
23from ...sql.sqltypes import TEXT
24from ...sql.visitors import InternalTraversal
25
26_T = TypeVar("_T", bound=Any)
27
28if TYPE_CHECKING:
29    from ...sql.visitors import _TraverseInternalsType
30
31
32class aggregate_order_by(expression.ColumnElement):
33    """Represent a PostgreSQL aggregate order by expression.
34
35    E.g.::
36
37        from sqlalchemy.dialects.postgresql import aggregate_order_by
38        expr = func.array_agg(aggregate_order_by(table.c.a, table.c.b.desc()))
39        stmt = select(expr)
40
41    would represent the expression::
42
43        SELECT array_agg(a ORDER BY b DESC) FROM table;
44
45    Similarly::
46
47        expr = func.string_agg(
48            table.c.a,
49            aggregate_order_by(literal_column("','"), table.c.a)
50        )
51        stmt = select(expr)
52
53    Would represent::
54
55        SELECT string_agg(a, ',' ORDER BY a) FROM table;
56
57    .. versionchanged:: 1.2.13 - the ORDER BY argument may be multiple terms
58
59    .. seealso::
60
61        :class:`_functions.array_agg`
62
63    """
64
65    __visit_name__ = "aggregate_order_by"
66
67    stringify_dialect = "postgresql"
68    _traverse_internals: _TraverseInternalsType = [
69        ("target", InternalTraversal.dp_clauseelement),
70        ("type", InternalTraversal.dp_type),
71        ("order_by", InternalTraversal.dp_clauseelement),
72    ]
73
74    def __init__(self, target, *order_by):
75        self.target = coercions.expect(roles.ExpressionElementRole, target)
76        self.type = self.target.type
77
78        _lob = len(order_by)
79        if _lob == 0:
80            raise TypeError("at least one ORDER BY element is required")
81        elif _lob == 1:
82            self.order_by = coercions.expect(
83                roles.ExpressionElementRole, order_by[0]
84            )
85        else:
86            self.order_by = elements.ClauseList(
87                *order_by, _literal_as_text_role=roles.ExpressionElementRole
88            )
89
90    def self_group(self, against=None):
91        return self
92
93    def get_children(self, **kwargs):
94        return self.target, self.order_by
95
96    def _copy_internals(self, clone=elements._clone, **kw):
97        self.target = clone(self.target, **kw)
98        self.order_by = clone(self.order_by, **kw)
99
100    @property
101    def _from_objects(self):
102        return self.target._from_objects + self.order_by._from_objects
103
104
105class ExcludeConstraint(ColumnCollectionConstraint):
106    """A table-level EXCLUDE constraint.
107
108    Defines an EXCLUDE constraint as described in the `PostgreSQL
109    documentation`__.
110
111    __ https://www.postgresql.org/docs/current/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
112
113    """  # noqa
114
115    __visit_name__ = "exclude_constraint"
116
117    where = None
118    inherit_cache = False
119
120    create_drop_stringify_dialect = "postgresql"
121
122    @elements._document_text_coercion(
123        "where",
124        ":class:`.ExcludeConstraint`",
125        ":paramref:`.ExcludeConstraint.where`",
126    )
127    def __init__(self, *elements, **kw):
128        r"""
129        Create an :class:`.ExcludeConstraint` object.
130
131        E.g.::
132
133            const = ExcludeConstraint(
134                (Column('period'), '&&'),
135                (Column('group'), '='),
136                where=(Column('group') != 'some group'),
137                ops={'group': 'my_operator_class'}
138            )
139
140        The constraint is normally embedded into the :class:`_schema.Table`
141        construct
142        directly, or added later using :meth:`.append_constraint`::
143
144            some_table = Table(
145                'some_table', metadata,
146                Column('id', Integer, primary_key=True),
147                Column('period', TSRANGE()),
148                Column('group', String)
149            )
150
151            some_table.append_constraint(
152                ExcludeConstraint(
153                    (some_table.c.period, '&&'),
154                    (some_table.c.group, '='),
155                    where=some_table.c.group != 'some group',
156                    name='some_table_excl_const',
157                    ops={'group': 'my_operator_class'}
158                )
159            )
160
161        The exclude constraint defined in this example requires the
162        ``btree_gist`` extension, that can be created using the
163        command ``CREATE EXTENSION btree_gist;``.
164
165        :param \*elements:
166
167          A sequence of two tuples of the form ``(column, operator)`` where
168          "column" is either a :class:`_schema.Column` object, or a SQL
169          expression element (e.g. ``func.int8range(table.from, table.to)``)
170          or the name of a column as string, and "operator" is a string
171          containing the operator to use (e.g. `"&&"` or `"="`).
172
173          In order to specify a column name when a :class:`_schema.Column`
174          object is not available, while ensuring
175          that any necessary quoting rules take effect, an ad-hoc
176          :class:`_schema.Column` or :func:`_expression.column`
177          object should be used.
178          The ``column`` may also be a string SQL expression when
179          passed as :func:`_expression.literal_column` or
180          :func:`_expression.text`
181
182        :param name:
183          Optional, the in-database name of this constraint.
184
185        :param deferrable:
186          Optional bool.  If set, emit DEFERRABLE or NOT DEFERRABLE when
187          issuing DDL for this constraint.
188
189        :param initially:
190          Optional string.  If set, emit INITIALLY <value> when issuing DDL
191          for this constraint.
192
193        :param using:
194          Optional string.  If set, emit USING <index_method> when issuing DDL
195          for this constraint. Defaults to 'gist'.
196
197        :param where:
198          Optional SQL expression construct or literal SQL string.
199          If set, emit WHERE <predicate> when issuing DDL
200          for this constraint.
201
202        :param ops:
203          Optional dictionary.  Used to define operator classes for the
204          elements; works the same way as that of the
205          :ref:`postgresql_ops <postgresql_operator_classes>`
206          parameter specified to the :class:`_schema.Index` construct.
207
208          .. versionadded:: 1.3.21
209
210          .. seealso::
211
212            :ref:`postgresql_operator_classes` - general description of how
213            PostgreSQL operator classes are specified.
214
215        """
216        columns = []
217        render_exprs = []
218        self.operators = {}
219
220        expressions, operators = zip(*elements)
221
222        for (expr, column, strname, add_element), operator in zip(
223            coercions.expect_col_expression_collection(
224                roles.DDLConstraintColumnRole, expressions
225            ),
226            operators,
227        ):
228            if add_element is not None:
229                columns.append(add_element)
230
231            name = column.name if column is not None else strname
232
233            if name is not None:
234                # backwards compat
235                self.operators[name] = operator
236
237            render_exprs.append((expr, name, operator))
238
239        self._render_exprs = render_exprs
240
241        ColumnCollectionConstraint.__init__(
242            self,
243            *columns,
244            name=kw.get("name"),
245            deferrable=kw.get("deferrable"),
246            initially=kw.get("initially"),
247        )
248        self.using = kw.get("using", "gist")
249        where = kw.get("where")
250        if where is not None:
251            self.where = coercions.expect(roles.StatementOptionRole, where)
252
253        self.ops = kw.get("ops", {})
254
255    def _set_parent(self, table, **kw):
256        super()._set_parent(table)
257
258        self._render_exprs = [
259            (
260                expr if not isinstance(expr, str) else table.c[expr],
261                name,
262                operator,
263            )
264            for expr, name, operator in (self._render_exprs)
265        ]
266
267    def _copy(self, target_table=None, **kw):
268        elements = [
269            (
270                schema._copy_expression(expr, self.parent, target_table),
271                operator,
272            )
273            for expr, _, operator in self._render_exprs
274        ]
275        c = self.__class__(
276            *elements,
277            name=self.name,
278            deferrable=self.deferrable,
279            initially=self.initially,
280            where=self.where,
281            using=self.using,
282        )
283        c.dispatch._update(self.dispatch)
284        return c
285
286
287def array_agg(*arg, **kw):
288    """PostgreSQL-specific form of :class:`_functions.array_agg`, ensures
289    return type is :class:`_postgresql.ARRAY` and not
290    the plain :class:`_types.ARRAY`, unless an explicit ``type_``
291    is passed.
292
293    """
294    kw["_default_array_type"] = ARRAY
295    return functions.func.array_agg(*arg, **kw)
296
297
298class _regconfig_fn(functions.GenericFunction[_T]):
299    inherit_cache = True
300
301    def __init__(self, *args, **kwargs):
302        args = list(args)
303        if len(args) > 1:
304            initial_arg = coercions.expect(
305                roles.ExpressionElementRole,
306                args.pop(0),
307                name=getattr(self, "name", None),
308                apply_propagate_attrs=self,
309                type_=types.REGCONFIG,
310            )
311            initial_arg = [initial_arg]
312        else:
313            initial_arg = []
314
315        addtl_args = [
316            coercions.expect(
317                roles.ExpressionElementRole,
318                c,
319                name=getattr(self, "name", None),
320                apply_propagate_attrs=self,
321            )
322            for c in args
323        ]
324        super().__init__(*(initial_arg + addtl_args), **kwargs)
325
326
327class to_tsvector(_regconfig_fn):
328    """The PostgreSQL ``to_tsvector`` SQL function.
329
330    This function applies automatic casting of the REGCONFIG argument
331    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
332    and applies a return type of :class:`_postgresql.TSVECTOR`.
333
334    Assuming the PostgreSQL dialect has been imported, either by invoking
335    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
336    engine using ``create_engine("postgresql...")``,
337    :class:`_postgresql.to_tsvector` will be used automatically when invoking
338    ``sqlalchemy.func.to_tsvector()``, ensuring the correct argument and return
339    type handlers are used at compile and execution time.
340
341    .. versionadded:: 2.0.0rc1
342
343    """
344
345    inherit_cache = True
346    type = types.TSVECTOR
347
348
349class to_tsquery(_regconfig_fn):
350    """The PostgreSQL ``to_tsquery`` SQL function.
351
352    This function applies automatic casting of the REGCONFIG argument
353    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
354    and applies a return type of :class:`_postgresql.TSQUERY`.
355
356    Assuming the PostgreSQL dialect has been imported, either by invoking
357    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
358    engine using ``create_engine("postgresql...")``,
359    :class:`_postgresql.to_tsquery` will be used automatically when invoking
360    ``sqlalchemy.func.to_tsquery()``, ensuring the correct argument and return
361    type handlers are used at compile and execution time.
362
363    .. versionadded:: 2.0.0rc1
364
365    """
366
367    inherit_cache = True
368    type = types.TSQUERY
369
370
371class plainto_tsquery(_regconfig_fn):
372    """The PostgreSQL ``plainto_tsquery`` SQL function.
373
374    This function applies automatic casting of the REGCONFIG argument
375    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
376    and applies a return type of :class:`_postgresql.TSQUERY`.
377
378    Assuming the PostgreSQL dialect has been imported, either by invoking
379    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
380    engine using ``create_engine("postgresql...")``,
381    :class:`_postgresql.plainto_tsquery` will be used automatically when
382    invoking ``sqlalchemy.func.plainto_tsquery()``, ensuring the correct
383    argument and return type handlers are used at compile and execution time.
384
385    .. versionadded:: 2.0.0rc1
386
387    """
388
389    inherit_cache = True
390    type = types.TSQUERY
391
392
393class phraseto_tsquery(_regconfig_fn):
394    """The PostgreSQL ``phraseto_tsquery`` SQL function.
395
396    This function applies automatic casting of the REGCONFIG argument
397    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
398    and applies a return type of :class:`_postgresql.TSQUERY`.
399
400    Assuming the PostgreSQL dialect has been imported, either by invoking
401    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
402    engine using ``create_engine("postgresql...")``,
403    :class:`_postgresql.phraseto_tsquery` will be used automatically when
404    invoking ``sqlalchemy.func.phraseto_tsquery()``, ensuring the correct
405    argument and return type handlers are used at compile and execution time.
406
407    .. versionadded:: 2.0.0rc1
408
409    """
410
411    inherit_cache = True
412    type = types.TSQUERY
413
414
415class websearch_to_tsquery(_regconfig_fn):
416    """The PostgreSQL ``websearch_to_tsquery`` SQL function.
417
418    This function applies automatic casting of the REGCONFIG argument
419    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
420    and applies a return type of :class:`_postgresql.TSQUERY`.
421
422    Assuming the PostgreSQL dialect has been imported, either by invoking
423    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
424    engine using ``create_engine("postgresql...")``,
425    :class:`_postgresql.websearch_to_tsquery` will be used automatically when
426    invoking ``sqlalchemy.func.websearch_to_tsquery()``, ensuring the correct
427    argument and return type handlers are used at compile and execution time.
428
429    .. versionadded:: 2.0.0rc1
430
431    """
432
433    inherit_cache = True
434    type = types.TSQUERY
435
436
437class ts_headline(_regconfig_fn):
438    """The PostgreSQL ``ts_headline`` SQL function.
439
440    This function applies automatic casting of the REGCONFIG argument
441    to use the :class:`_postgresql.REGCONFIG` datatype automatically,
442    and applies a return type of :class:`_types.TEXT`.
443
444    Assuming the PostgreSQL dialect has been imported, either by invoking
445    ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL
446    engine using ``create_engine("postgresql...")``,
447    :class:`_postgresql.ts_headline` will be used automatically when invoking
448    ``sqlalchemy.func.ts_headline()``, ensuring the correct argument and return
449    type handlers are used at compile and execution time.
450
451    .. versionadded:: 2.0.0rc1
452
453    """
454
455    inherit_cache = True
456    type = TEXT
457
458    def __init__(self, *args, **kwargs):
459        args = list(args)
460
461        # parse types according to
462        # https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-HEADLINE
463        if len(args) < 2:
464            # invalid args; don't do anything
465            has_regconfig = False
466        elif (
467            isinstance(args[1], elements.ColumnElement)
468            and args[1].type._type_affinity is types.TSQUERY
469        ):
470            # tsquery is second argument, no regconfig argument
471            has_regconfig = False
472        else:
473            has_regconfig = True
474
475        if has_regconfig:
476            initial_arg = coercions.expect(
477                roles.ExpressionElementRole,
478                args.pop(0),
479                apply_propagate_attrs=self,
480                name=getattr(self, "name", None),
481                type_=types.REGCONFIG,
482            )
483            initial_arg = [initial_arg]
484        else:
485            initial_arg = []
486
487        addtl_args = [
488            coercions.expect(
489                roles.ExpressionElementRole,
490                c,
491                name=getattr(self, "name", None),
492                apply_propagate_attrs=self,
493            )
494            for c in args
495        ]
496        super().__init__(*(initial_arg + addtl_args), **kwargs)
497 
codekingpro/portable-devtools · Team Ai