ext.py 17 KB

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