--- /dev/null
+.. change::
+ :tags: usecase, postgresql
+ :tickets: 9965
+
+ The pg8000 dialect now supports RANGE and MULTIRANGE datatypes, using the
+ existing RANGE API described at :ref:`postgresql_ranges`. Range and
+ multirange types are supported in the pg8000 driver from version 1.29.8.
+ Pull request courtesy Tony Locke.
Range and Multirange Types
--------------------------
-PostgreSQL range and multirange types are supported for the psycopg2,
-psycopg, and asyncpg dialects.
+PostgreSQL range and multirange types are supported for the
+psycopg, pg8000 and asyncpg dialects; the psycopg2 dialect supports the
+range types only.
+
+.. versionadded:: 2.0.17 Added range and multirange support for the pg8000
+ dialect. pg8000 1.29.8 or greater is required.
Data values being passed to the database may be passed as string
values or by using the :class:`_postgresql.Range` data object.
Multiranges are supported by PostgreSQL 14 and above. SQLAlchemy's
multirange datatypes deal in lists of :class:`_postgresql.Range` types.
-.. versionadded:: 2.0 Added support for MULTIRANGE datatypes. In contrast
- to the ``psycopg`` multirange feature, SQLAlchemy's adaptation represents
- a multirange datatype as a list of :class:`_postgresql.Range` objects.
+Multiranges are supported on the psycopg, asyncpg, and pg8000 dialects
+**only**. The psycopg2 dialect, which is SQLAlchemy's default ``postgresql``
+dialect, **does not** support multirange datatypes.
+
+.. versionadded:: 2.0 Added support for MULTIRANGE datatypes.
+ SQLAlchemy represents a multirange value as a list of
+ :class:`_postgresql.Range` objects.
+
+.. versionadded:: 2.0.17 Added multirange support for the pg8000 dialect.
+ pg8000 1.29.8 or greater is required.
The example below illustrates use of the :class:`_postgresql.TSMULTIRANGE`
datatype::
import decimal
import re
+from . import ranges
from .array import ARRAY as PGARRAY
from .base import _DECIMAL_TYPES
from .base import _FLOAT_TYPES
pass
+class _Pg8000Range(ranges.AbstractRangeImpl):
+ def bind_processor(self, dialect):
+ pg8000_Range = dialect.dbapi.Range
+
+ def to_range(value):
+ if isinstance(value, ranges.Range):
+ value = pg8000_Range(
+ value.lower, value.upper, value.bounds, value.empty
+ )
+ return value
+
+ return to_range
+
+ def result_processor(self, dialect, coltype):
+ def to_range(value):
+ if value is not None:
+ value = ranges.Range(
+ value.lower,
+ value.upper,
+ bounds=value.bounds,
+ empty=value.is_empty,
+ )
+ return value
+
+ return to_range
+
+
+class _Pg8000MultiRange(ranges.AbstractMultiRangeImpl):
+ def bind_processor(self, dialect):
+ pg8000_Range = dialect.dbapi.Range
+
+ def to_multirange(value):
+ if isinstance(value, list):
+ mr = []
+ for v in value:
+ if isinstance(v, ranges.Range):
+ mr.append(
+ pg8000_Range(v.lower, v.upper, v.bounds, v.empty)
+ )
+ else:
+ mr.append(v)
+ return mr
+ else:
+ return value
+
+ return to_multirange
+
+ def result_processor(self, dialect, coltype):
+ def to_multirange(value):
+ if value is None:
+ return None
+
+ mr = []
+ for v in value:
+ mr.append(
+ ranges.Range(
+ v.lower, v.upper, bounds=v.bounds, empty=v.is_empty
+ )
+ )
+ return mr
+
+ return to_multirange
+
+
_server_side_id = util.counter()
sqltypes.Enum: _PGEnum,
sqltypes.ARRAY: _PGARRAY,
OIDVECTOR: _PGOIDVECTOR,
+ ranges.INT4RANGE: _Pg8000Range,
+ ranges.INT8RANGE: _Pg8000Range,
+ ranges.NUMRANGE: _Pg8000Range,
+ ranges.DATERANGE: _Pg8000Range,
+ ranges.TSRANGE: _Pg8000Range,
+ ranges.TSTZRANGE: _Pg8000Range,
+ ranges.INT4MULTIRANGE: _Pg8000MultiRange,
+ ranges.INT8MULTIRANGE: _Pg8000MultiRange,
+ ranges.NUMMULTIRANGE: _Pg8000MultiRange,
+ ranges.DATEMULTIRANGE: _Pg8000MultiRange,
+ ranges.TSMULTIRANGE: _Pg8000MultiRange,
+ ranges.TSTZMULTIRANGE: _Pg8000MultiRange,
},
)
)
self._assert_data(connection)
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_insert_text(self, connection):
connection.execute(
self.tables.data_table.insert(), {"range": self._data_str()}
data = connection.execute(select(range_ + range_)).fetchall()
eq_(data, [(self._data_obj(),)])
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_union_result_text(self, connection):
# insert
connection.execute(
data = connection.execute(select(range_ * range_)).fetchall()
eq_(data, [(self._data_obj(),)])
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_intersection_result_text(self, connection):
# insert
connection.execute(
data = connection.execute(select(range_ - range_)).fetchall()
eq_(data, [(self._data_obj().__class__(empty=True),)])
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_difference_result_text(self, connection):
# insert
connection.execute(
)
self._assert_data(connection)
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_insert_text(self, connection):
connection.execute(
self.tables.data_table.insert(), {"range": self._data_str()}
)
self._assert_data(connection)
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_union_result_text(self, connection):
# insert
connection.execute(
data = connection.execute(select(range_ + range_)).fetchall()
eq_(data, [(self._data_obj(),)])
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_intersection_result_text(self, connection):
# insert
connection.execute(
data = connection.execute(select(range_ * range_)).fetchall()
eq_(data, [(self._data_obj(),)])
- @testing.requires.any_psycopg_compatibility
+ @testing.requires.psycopg_or_pg8000_compatibility
def test_difference_result_text(self, connection):
# insert
connection.execute(
@property
def range_types(self):
- return only_on(["+psycopg2", "+psycopg", "+asyncpg"])
+ return only_on(["+psycopg2", "+psycopg", "+asyncpg", "+pg8000"])
@property
def multirange_types(self):
- return only_on(["+psycopg", "+asyncpg"]) + only_on("postgresql >= 14")
+ return only_on(["+psycopg", "+asyncpg", "+pg8000"]) + only_on(
+ "postgresql >= 14"
+ )
@property
def async_dialect(self):