From: Mike Bayer Date: Wed, 5 Apr 2017 16:55:39 +0000 (-0400) Subject: Support Postgresql INTERVAL fields spec/reflection X-Git-Tag: rel_1_2_0b1~110 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b39b6023c57b25c00bbed946a83bc44c36d52940;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Support Postgresql INTERVAL fields spec/reflection 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 --- diff --git a/doc/build/changelog/changelog_12.rst b/doc/build/changelog/changelog_12.rst index 0c13603036..2070b92c03 100644 --- a/doc/build/changelog/changelog_12.rst +++ b/doc/build/changelog/changelog_12.rst @@ -31,6 +31,22 @@ :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 diff --git a/doc/build/changelog/migration_12.rst b/doc/build/changelog/migration_12.rst index 203b4e5eda..1dda856696 100644 --- a/doc/build/changelog/migration_12.rst +++ b/doc/build/changelog/migration_12.rst @@ -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 ============================================= diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 92009450e7..802c6a905b 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -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),) diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index e02c8915d8..a1942ab303 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -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) +