summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2017-02-01 16:25:31 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2017-02-01 17:00:42 -0500
commit388d8db68d0db6f38e72d60386023c9eef263034 (patch)
tree1deae94a0a2c8ac4a066da157ee6c36ecfc409ce
parent9a5943bf76cd436484a85a6d9478507c9bac3b08 (diff)
downloadsqlalchemy-388d8db68d0db6f38e72d60386023c9eef263034.tar.gz
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
-rw-r--r--doc/build/changelog/changelog_11.rst11
-rw-r--r--lib/sqlalchemy/engine/reflection.py34
-rw-r--r--lib/sqlalchemy/events.py24
-rw-r--r--test/engine/test_reflection.py53
4 files changed, 101 insertions, 21 deletions
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst
index 8ed93ef74..9b22a123c 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 bd250131d..dfa81f4ca 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 2ed44f5dd..7aa30015c 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 0bc5b111e..9ddfe940f 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
+ )