ext.py 17 KB

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