]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Add Multirange documentation
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 4 Oct 2021 12:43:15 +0000 (14:43 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 4 Oct 2021 12:45:56 +0000 (14:45 +0200)
docs/basic/pgtypes.rst
docs/news.rst
psycopg/psycopg/types/multirange.py

index 28372379dab795e4730a9030e2a5d4a0586477ac..bb19e5efb7658f162ca9b80acb9b1a884d99c693 100644 (file)
@@ -116,6 +116,10 @@ different types.
     features: it doesn't perform normalization and doesn't implement all the
     operators__ supported by the database.
 
+    PostgreSQL will perform normalisation on `!Range` objects used as query
+    parameters, so, when they are fetched back, they will be found in the
+    normal form (for instance ranges on integers will have `[)` bounds).
+
     .. __: https://www.postgresql.org/docs/current/static/functions-range.html#RANGE-OPERATORS-TABLE
 
     `!Range` objects are immutable, hashable, and support the ``in`` operator
@@ -151,8 +155,8 @@ its subtype and make it work like the builtin ones.
 Example::
 
     >>> from psycopg.types.range import Range, RangeInfo, register_range
-    >>> conn.execute("create type strrange as range (subtype = text)")
 
+    >>> conn.execute("CREATE TYPE strrange AS RANGE (SUBTYPE = text)")
     >>> info = RangeInfo.fetch(conn, "strrange")
     >>> register_range(info, conn)
 
@@ -163,6 +167,81 @@ Example::
     Range('a', 'z', '[]')
 
 
+.. index::
+    pair: range; Data types
+
+.. _adapt-multirange:
+
+Multirange adaptation
+---------------------
+
+Since PostgreSQL 14, every range type is associated with a multirange__, a
+type representing a disjoint set of ranges. A multirange is
+automatically available for every range, built-in and user-defined.
+
+.. __: https://www.postgresql.org/docs/current/rangetypes.html
+
+All the PostgreSQL range types are loaded as the
+`~psycopg.types.multirange.Multirange` Python type, which is a mutable
+sequence of `~psycopg.types.range.Range` elements.
+
+.. autoclass:: psycopg.types.multirange.Multirange
+
+    This Python type is only used to pass and retrieve multirange values to
+    and from PostgreSQL and doesn't attempt to replicate the PostgreSQL
+    multirange features: overlapping items are not merged, empty ranges are
+    not discarded, the items are not ordered, the behaviour of `multirange
+    operators`__ is not replicated in Python.
+
+    PostgreSQL will perform normalisation on `!Multirange` objects used as
+    query parameters, so, when they are fetched back, they will be found
+    ordered, with overlapping ranges merged, etc.
+
+    .. __: https://www.postgresql.org/docs/current/static/functions-range.html#MULTIRANGE-OPERATORS-TABLE
+
+    `!Multirange` objects are a `~collections.abc.MutableSequence` and are
+    totally ordered: they behave pretty much like a list of `!Range`. Like
+    Range, they are `~typing.Generic` on the subtype of their range, so you
+    can declare a variable to be `!Multitype[date]` and mypy will complain if
+    you try to add it a `Range[Decimal]`.
+
+Like for `~psycopg.types.range.Range`, built-in multirange objects are adapted
+automatically: if a `!Multirange` objects contains `!Range` with
+`~datetime.date` bounds, it is dumped using the :sql:`datemultirange` OID, and
+:sql:`datemultirange` values are loaded back as `!Multirange[date]`.
+
+If you have created your own range type you can use
+`~psycopg.types.multirange.MultirangeInfo` and
+`~psycopg.types.multirange.register_multirange()` to associate the resulting
+multirange type with its subtype and make it work like the builtin ones.
+
+.. autoclass:: psycopg.types.multirange.MultirangeInfo
+
+   `!MultirangeInfo` is a `~psycopg.types.TypeInfo` subclass: check its
+   documentation for generic details.
+
+.. autofunction:: psycopg.types.multirange.register_multirange
+
+Example::
+
+    >>> from psycopg.types.multirange import \
+    ...     Multirange, MultirangeInfo, register_multirange
+    >>> from psycopg.types.range import Range
+
+    >>> conn.execute("CREATE TYPE strrange AS RANGE (SUBTYPE = text)")
+    >>> info = MultirangeInfo.fetch(conn, "strmultirange")
+    >>> register_multirange(info, conn)
+
+    >>> rec = conn.execute(
+    ...     "SELECT pg_typeof(%(mr)s), %(mr)s",
+    ...     {"mr": Multirange([Range("a", "q"), Range("l", "z")])}).fetchone()
+
+    >>> rec[0]
+    'strmultirange'
+    >>> rec[1]
+    Multirange([Range('a', 'z', '[)')])
+
+
 .. index::
     pair: hstore; Data types
     pair: dict; Adaptation
index ba5e40331e8b98c0dc574a2aa00a251da575388d..78bdc097ca38df9363bfed889eb4e5e0bdd47f52 100644 (file)
@@ -14,6 +14,7 @@ psycopg 3.0b2
 ^^^^^^^^^^^^^
 
 - Add :ref:`adapt-shapely` (:ticket:`#80`).
+- Add :ref:`adapt-multirange` (:ticket:`#75`).
 - Add `pq.__build_version__` constant.
 - Don't use the extended protocol with COPY, (:tickets:`#78, #82`).
 - Add *context* parameter to `~Connection.connect()` (:ticket:`#83`).
index 62bcd04b26f45401d9ac8e25c0d8c4d929ac5f62..7824e230efd9a01b5261709b04bd618f20aacbfb 100644 (file)
@@ -23,6 +23,11 @@ from .range import dump_range_text, dump_range_binary, fail_dump
 
 
 class Multirange(MutableSequence[Range[T]]):
+    """Python representation for a PostgreSQL multirange type.
+
+    :param items: Sequence of ranges to initialise the object.
+    """
+
     def __init__(self, items: Iterable[Range[T]] = ()):
         self._ranges: List[Range[T]] = list(map(self._check_type, items))