]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Accept FetchedValue, text() for column "default" value
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 1 Feb 2017 21:25:31 +0000 (16:25 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 1 Feb 2017 22:00:42 +0000 (17:00 -0500)
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

doc/build/changelog/changelog_11.rst
lib/sqlalchemy/engine/reflection.py
lib/sqlalchemy/events.py
test/engine/test_reflection.py

index 8ed93ef74c4aafa047eea0af9e2f1f7f563c4313..9b22a123c292e5f3eeea092429e473e1094a01e0 100644 (file)
 .. 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
index bd250131db0c1e35bfc027e307648561c6eb5f6a..dfa81f4ca301145c48f407d5c30a6600b7ce1563 100644 (file)
@@ -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)
index 2ed44f5dd8e72ec2968c281e623df16a07df1178..7aa30015cfc4a3ce8ba5fd1ad6a4a557691d067f 100644 (file)
@@ -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.
index 0bc5b111e1ace3122e9c789820fd0e5a81853479..9ddfe940f48c22c2f4a89e3720db86c77efe972b 100644 (file)
@@ -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
+        )