From: Mike Bayer Date: Wed, 1 Feb 2017 21:25:31 +0000 (-0500) Subject: Accept FetchedValue, text() for column "default" value X-Git-Tag: rel_1_1_6~14^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=388d8db68d0db6f38e72d60386023c9eef263034;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Accept FetchedValue, text() for column "default" value Fixed bug whereby the :meth:`.DDLEvents.column_reflect` event would not allow a non-textual expression to be passed as the value of the "default" for the new column, such as a :class:`.FetchedValue` object to indicate a generic triggered default or a :func:`.sql.expression.text` construct. Clarified the documentation in this regard as well. Fixes: #3905 Change-Id: I829796c3e9f87f375149bebee7eef133a6876d4d --- diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 8ed93ef74c..9b22a123c2 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -21,6 +21,17 @@ .. changelog:: :version: 1.1.6 + .. change:: 3905 + :tags: bug, sql + :tickets: 3905 + + Fixed bug whereby the :meth:`.DDLEvents.column_reflect` event would not + allow a non-textual expression to be passed as the value of the + "default" for the new column, such as a :class:`.FetchedValue` + object to indicate a generic triggered default or a + :func:`.sql.expression.text` construct. Clarified the documentation + in this regard as well. + .. change:: 3901 :tags: bug, ext :tickets: 3901 diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py index bd250131db..dfa81f4ca3 100644 --- a/lib/sqlalchemy/engine/reflection.py +++ b/lib/sqlalchemy/engine/reflection.py @@ -340,20 +340,17 @@ class Inspector(object): Given a string `table_name` and an optional string `schema`, return column information as a list of dicts with these keys: - name - the column's name + * ``name`` - the column's name - type + * ``type`` - the type of this column; an instance of :class:`~sqlalchemy.types.TypeEngine` - nullable - boolean + * ``nullable`` - boolean flag if the column is NULL or NOT NULL - default - the column's default value + * ``default`` - the column's server default value - this is returned + as a string SQL expression. - attrs - dict containing optional column attributes + * ``attrs`` - dict containing optional column attributes :param table_name: string name of the table. For special quoting, use :class:`.quoted_name`. @@ -362,6 +359,9 @@ class Inspector(object): of the database connection. For special quoting, use :class:`.quoted_name`. + :return: list of dictionaries, each representing the definition of + a database column. + """ col_defs = self.dialect.get_columns(self.bind, table_name, schema, @@ -649,14 +649,14 @@ class Inspector(object): colargs = [] if col_d.get('default') is not None: - # the "default" value is assumed to be a literal SQL - # expression, so is wrapped in text() so that no quoting - # occurs on re-issuance. - colargs.append( - sa_schema.DefaultClause( - sql.text(col_d['default']), _reflected=True - ) - ) + default = col_d['default'] + if isinstance(default, sql.elements.TextClause): + default = sa_schema.DefaultClause(default, _reflected=True) + elif not isinstance(default, sa_schema.FetchedValue): + default = sa_schema.DefaultClause( + sql.text(col_d['default']), _reflected=True) + + colargs.append(default) if 'sequence' in col_d: self._reflect_col_sequence(col_d, colargs) diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py index 2ed44f5dd8..7aa30015cf 100644 --- a/lib/sqlalchemy/events.py +++ b/lib/sqlalchemy/events.py @@ -178,7 +178,29 @@ class DDLEvents(event.Events): The dictionary of column information as returned by the dialect is passed, and can be modified. The dictionary is that returned in each element of the list returned - by :meth:`.reflection.Inspector.get_columns`. + by :meth:`.reflection.Inspector.get_columns`: + + * ``name`` - the column's name + + * ``type`` - the type of this column, which should be an instance + of :class:`~sqlalchemy.types.TypeEngine` + + * ``nullable`` - boolean flag if the column is NULL or NOT NULL + + * ``default`` - the column's server default value. This is + normally specified as a plain string SQL expression, however the + event can pass a :class:`.FetchedValue`, :class:`.DefaultClause`, + or :func:`.sql.expression.text` object as well. + + .. versionchanged:: 1.1.6 + + The :meth:`.DDLEvents.column_reflect` event allows a non + string :class:`.FetchedValue`, + :func:`.sql.expression.text`, or derived object to be + specified as the value of ``default`` in the column + dictionary. + + * ``attrs`` - dict containing optional column attributes The event is called before any action is taken against this dictionary, and the contents can be modified. diff --git a/test/engine/test_reflection.py b/test/engine/test_reflection.py index 0bc5b111e1..9ddfe940f4 100644 --- a/test/engine/test_reflection.py +++ b/test/engine/test_reflection.py @@ -1,13 +1,14 @@ import unicodedata import sqlalchemy as sa -from sqlalchemy import schema, inspect +from sqlalchemy import schema, inspect, sql from sqlalchemy import MetaData, Integer, String, Index, ForeignKey, \ - UniqueConstraint + UniqueConstraint, FetchedValue, DefaultClause from sqlalchemy.testing import ( ComparesTables, engines, AssertsCompiledSQL, fixtures, skip) from sqlalchemy.testing.schema import Table, Column -from sqlalchemy.testing import eq_, assert_raises, assert_raises_message +from sqlalchemy.testing import eq_, is_true, assert_raises, \ + assert_raises_message from sqlalchemy import testing from sqlalchemy.util import ue from sqlalchemy.testing import config @@ -1770,3 +1771,49 @@ class ColumnEventsTest(fixtures.RemovesEvents, fixtures.TestBase): "x", {"info": {"a": "b"}}, lambda table: eq_(table.c.x.info, {"a": "b"}) ) + + def test_override_server_default_fetchedvalue(self): + my_default = FetchedValue() + self._do_test( + "x", {"default": my_default}, + lambda table: eq_(table.c.x.server_default, my_default) + ) + + def test_override_server_default_default_clause(self): + my_default = DefaultClause("1") + self._do_test( + "x", {"default": my_default}, + lambda table: eq_(table.c.x.server_default, my_default) + ) + + def test_override_server_default_plain_text(self): + my_default = "1" + + def assert_text_of_one(table): + is_true( + isinstance( + table.c.x.server_default.arg, sql.elements.TextClause) + ) + eq_( + str(table.c.x.server_default.arg), "1" + ) + self._do_test( + "x", {"default": my_default}, + assert_text_of_one + ) + + def test_override_server_default_textclause(self): + my_default = sa.text("1") + + def assert_text_of_one(table): + is_true( + isinstance( + table.c.x.server_default.arg, sql.elements.TextClause) + ) + eq_( + str(table.c.x.server_default.arg), "1" + ) + self._do_test( + "x", {"default": my_default}, + assert_text_of_one + )