]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Support Postgresql INTERVAL fields spec/reflection
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 5 Apr 2017 16:55:39 +0000 (12:55 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 5 Apr 2017 17:55:10 +0000 (13:55 -0400)
Added support for all possible "fields" identifiers when reflecting the
Postgresql ``INTERVAL`` datatype, e.g. "YEAR", "MONTH", "DAY TO
MINUTE", etc..   In addition, the :class:`.postgresql.INTERVAL`
datatype itself now includes a new parameter
:paramref:`.postgresql.INTERVAL.fields` where these qualifiers can be
specified; the qualifier is also reflected back into the resulting
datatype upon reflection / inspection.

Change-Id: I33816e68c533b023e0632db6f4e73fefd2de4721
Fixes: #3959
doc/build/changelog/changelog_12.rst
doc/build/changelog/migration_12.rst
lib/sqlalchemy/dialects/postgresql/base.py
test/dialect/postgresql/test_reflection.py

index 0c13603036e74999b01b7a873f361b8e01c88379..2070b92c03e8391fd2537efdb8a6224ba9bb76f9 100644 (file)
 
             :ref:`change_3740`
 
+    .. change:: 3959
+        :tags: bug, postgresql
+        :tickets: 3959
+
+        Added support for all possible "fields" identifiers when reflecting the
+        Postgresql ``INTERVAL`` datatype, e.g. "YEAR", "MONTH", "DAY TO
+        MINUTE", etc..   In addition, the :class:`.postgresql.INTERVAL`
+        datatype itself now includes a new parameter
+        :paramref:`.postgresql.INTERVAL.fields` where these qualifiers can be
+        specified; the qualifier is also reflected back into the resulting
+        datatype upon reflection / inspection.
+
+        .. seealso::
+
+            :ref:`change_3959`
+
     .. change:: 3957
         :tags: bug, sql
         :tickets: 3957
index 203b4e5eda8080e6276f446bae8bf7360e86cd82..1dda856696a65a50eb7ab687725862665b647ad8 100644 (file)
@@ -645,6 +645,35 @@ is already applied.
 Dialect Improvements and Changes - PostgreSQL
 =============================================
 
+.. _change_3959:
+
+Support for fields specification in INTERVAL, including full reflection
+-----------------------------------------------------------------------
+
+The "fields" specifier in Postgresql's INTERVAL datatype allows specification
+of which fields of the interval to store, including such values as "YEAR",
+"MONTH", "YEAR TO MONTH", etc.   The :class:`.postgresql.INTERVAL` datatype
+now allows these values to be specified::
+
+    from sqlalchemy.dialects.postgresql import INTERVAL
+
+    Table(
+        'my_table', metadata,
+        Column("some_interval", INTERVAL(fields="DAY TO SECOND"))
+    )
+
+Additionally, all INTERVAL datatypes can now be reflected independently
+of the "fields" specifier present; the "fields" parameter in the datatype
+itself will also be present::
+
+    >>> inspect(engine).get_columns("my_table")
+    [{'comment': None,
+      'name': u'some_interval', 'nullable': True,
+      'default': None, 'autoincrement': False,
+      'type': INTERVAL(fields=u'day to second')}]
+
+:ticket:`3959`
+
 Dialect Improvements and Changes - MySQL
 =============================================
 
index 92009450e7329120fd34ef9bd29edfab95faeed7..802c6a905b829a34dba64d04bbf4d78cd61f47b2 100644 (file)
@@ -962,8 +962,19 @@ class INTERVAL(sqltypes.TypeEngine):
     """
     __visit_name__ = 'INTERVAL'
 
-    def __init__(self, precision=None):
+    def __init__(self, precision=None, fields=None):
+        """Construct an INTERVAL.
+
+        :param precision: optional integer precision value
+        :param fields: string fields specifier.  allows storage of fields
+         to be limited, such as ``"YEAR"``, ``"MONTH"``, ``"DAY TO HOUR"``,
+         etc.
+
+         .. versionadded:: 1.2
+
+        """
         self.precision = precision
+        self.fields = fields
 
     @classmethod
     def _adapt_from_generic_interval(cls, interval):
@@ -1301,8 +1312,6 @@ ischema_names = {
     'bytea': BYTEA,
     'boolean': BOOLEAN,
     'interval': INTERVAL,
-    'interval year to month': INTERVAL,
-    'interval day to second': INTERVAL,
     'tsvector': TSVECTOR
 }
 
@@ -1827,10 +1836,12 @@ class PGTypeCompiler(compiler.GenericTypeCompiler):
         )
 
     def visit_INTERVAL(self, type_, **kw):
+        text = "INTERVAL"
+        if type_.fields is not None:
+            text += " " + type_.fields
         if type_.precision is not None:
-            return "INTERVAL(%d)" % type_.precision
-        else:
-            return "INTERVAL"
+            text += " (%d)" % type_.precision
+        return text
 
     def visit_BIT(self, type_, **kw):
         if type_.varying:
@@ -2489,10 +2500,13 @@ class PGDialect(default.DefaultDialect):
                 args = (int(charlen),)
             else:
                 args = ()
-        elif attype in ('interval', 'interval year to month',
-                        'interval day to second'):
+        elif attype.startswith('interval'):
+            field_match = re.match(r'interval (.+)', attype, re.I)
             if charlen:
                 kwargs['precision'] = int(charlen)
+            if field_match:
+                kwargs['fields'] = field_match.group(1)
+            attype = "interval"
             args = ()
         elif charlen:
             args = (int(charlen),)
index e02c8915d807845a519e26c79fb55736968c10ab..a1942ab30323451af341b524db0a6812966ccbb6 100644 (file)
@@ -13,7 +13,7 @@ from sqlalchemy import Table, Column, MetaData, Integer, String, \
 from sqlalchemy import exc
 import sqlalchemy as sa
 from sqlalchemy.dialects.postgresql import base as postgresql
-from sqlalchemy.dialects.postgresql import ARRAY
+from sqlalchemy.dialects.postgresql import ARRAY, INTERVAL
 import re
 
 
@@ -1069,3 +1069,61 @@ class CustomTypeReflectionTest(fixtures.TestBase):
         dialect.ischema_names = dialect.ischema_names.copy()
         dialect.ischema_names['my_custom_type'] = self.CustomType
         self._assert_reflected(dialect)
+
+
+class IntervalReflectionTest(fixtures.TestBase):
+    __only_on__ = 'postgresql'
+    __backend__ = True
+
+    def test_interval_types(self):
+        for sym in [
+            "YEAR",
+            "MONTH",
+            "DAY",
+            "HOUR",
+            "MINUTE",
+            "SECOND",
+            "YEAR TO MONTH",
+            "DAY TO HOUR",
+            "DAY TO MINUTE",
+            "DAY TO SECOND",
+            "HOUR TO MINUTE",
+            "HOUR TO SECOND",
+            "MINUTE TO SECOND",
+        ]:
+            self._test_interval_symbol(sym)
+
+    @testing.provide_metadata
+    def _test_interval_symbol(self, sym):
+        t = Table(
+            'i_test', self.metadata,
+            Column('id', Integer, primary_key=True),
+            Column('data1', INTERVAL(fields=sym)),
+        )
+        t.create(testing.db)
+
+        columns = {
+            rec['name']: rec
+            for rec in inspect(testing.db).get_columns("i_test")
+        }
+        assert isinstance(columns["data1"]["type"], INTERVAL)
+        eq_(columns["data1"]["type"].fields, sym.lower())
+        eq_(columns["data1"]["type"].precision, None)
+
+    @testing.provide_metadata
+    def test_interval_precision(self):
+        t = Table(
+            'i_test', self.metadata,
+            Column('id', Integer, primary_key=True),
+            Column('data1', INTERVAL(precision=6)),
+        )
+        t.create(testing.db)
+
+        columns = {
+            rec['name']: rec
+            for rec in inspect(testing.db).get_columns("i_test")
+        }
+        assert isinstance(columns["data1"]["type"], INTERVAL)
+        eq_(columns["data1"]["type"].fields, None)
+        eq_(columns["data1"]["type"].precision, 6)
+