summaryrefslogtreecommitdiff
path: root/alembic/autogenerate
diff options
context:
space:
mode:
authorCaselIT <cfederico87@gmail.com>2019-12-27 17:26:38 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2020-01-01 15:11:00 -0500
commit3ea190f843c7f246f73f91a9b79ede63d8d610fb (patch)
tree3f9109e9189189315c15c081d2d2c6e47842bba9 /alembic/autogenerate
parent286fbcf2408d0264500e0527c3cb9d2f41cb5b1e (diff)
downloadalembic-3ea190f843c7f246f73f91a9b79ede63d8d610fb.tar.gz
Add support for computed columns
Added support for rendering of "computed" elements on :class:`.Column` objects, supported in SQLAlchemy via the new :class:`.Computed` element introduced in version 1.3.11. Pull request courtesy Federico Caselli. Note that there is currently no support for ALTER COLUMN to add, remove, or modify the "GENERATED ALWAYS AS" element from a column; at least for PostgreSQL, it does not seem to be supported by the database. Additionally, SQLAlchemy does not currently reliably reflect the "GENERATED ALWAYS AS" phrase from an existing column, so there is also no autogenerate support for addition or removal of the :class:`.Computed` element to or from an existing column, there is only support for adding new columns that include the :class:`.Computed` element. In the case that the :class:`.Computed` element is removed from the :class:`.Column` object in the table metadata, PostgreSQL and Oracle currently reflect the "GENERATED ALWAYS AS" expression as the "server default" which will produce an op that tries to drop the element as a default. In order to facilitate using testing combinations with elements that are not necessarily present, the support for lambda combinations from SQLAlchemy Ia63a510f9c1d08b055eef62cf047f1f427f0450c is also ported here, as well as a vendoring in of the current sqlalchemy.testing.exclusions module where we need the additional combinations support added in I15d2839954d77a252bab5aaf6e3fd9f388c99dd5. Fixes: #624 Closes: #631 Pull-request: https://github.com/sqlalchemy/alembic/pull/631 Pull-request-sha: 9c45651295626edf5f172ec827a5ced1440818cf Change-Id: Ifd27c2f541b22fb7a78de1afaa36dbf509ff6d3f
Diffstat (limited to 'alembic/autogenerate')
-rw-r--r--alembic/autogenerate/compare.py12
-rw-r--r--alembic/autogenerate/render.py39
2 files changed, 44 insertions, 7 deletions
diff --git a/alembic/autogenerate/compare.py b/alembic/autogenerate/compare.py
index cacedee..71f947e 100644
--- a/alembic/autogenerate/compare.py
+++ b/alembic/autogenerate/compare.py
@@ -910,6 +910,18 @@ def _compare_server_default(
conn_col_default = conn_col.server_default
if conn_col_default is None and metadata_default is None:
return False
+
+ if sqla_compat.has_computed and isinstance(
+ metadata_default, sa_schema.Computed
+ ):
+ # return False in case of a computed column as the server
+ # default. Note that DDL for adding or removing "GENERATED AS" from
+ # an existing column is not currently known for any backend.
+ # Once SQLAlchemy can reflect "GENERATED" as the "computed" element,
+ # we would also want to ignore and/or warn for changes vs. the
+ # metadata (or support backend specific DDL if applicable).
+ return False
+
rendered_metadata_default = _render_server_default_for_compare(
metadata_default, metadata_col, autogen_context
)
diff --git a/alembic/autogenerate/render.py b/alembic/autogenerate/render.py
index d9aba00..99c15c3 100644
--- a/alembic/autogenerate/render.py
+++ b/alembic/autogenerate/render.py
@@ -593,13 +593,20 @@ def _render_column(column, autogen_context):
if rendered is not False:
return rendered
+ args = []
opts = []
+
if column.server_default:
- rendered = _render_server_default(
- column.server_default, autogen_context
- )
- if rendered:
- opts.append(("server_default", rendered))
+ if sqla_compat._server_default_is_computed(column):
+ rendered = _render_computed(column.computed, autogen_context)
+ if rendered:
+ args.append(rendered)
+ else:
+ rendered = _render_server_default(
+ column.server_default, autogen_context
+ )
+ if rendered:
+ opts.append(("server_default", rendered))
if (
column.autoincrement is not None
@@ -618,10 +625,11 @@ def _render_column(column, autogen_context):
opts.append(("comment", "%r" % comment))
# TODO: for non-ascii colname, assign a "key"
- return "%(prefix)sColumn(%(name)r, %(type)s, %(kwargs)s)" % {
+ return "%(prefix)sColumn(%(name)r, %(type)s, %(args)s%(kwargs)s)" % {
"prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
"name": _ident(column.name),
"type": _repr_type(column.type, autogen_context),
+ "args": ", ".join([str(arg) for arg in args]) + ", " if args else "",
"kwargs": (
", ".join(
["%s=%s" % (kwname, val) for kwname, val in opts]
@@ -640,7 +648,9 @@ def _render_server_default(default, autogen_context, repr_=True):
if rendered is not False:
return rendered
- if isinstance(default, sa_schema.DefaultClause):
+ if sqla_compat.has_computed and isinstance(default, sa_schema.Computed):
+ return _render_computed(default, autogen_context)
+ elif isinstance(default, sa_schema.DefaultClause):
if isinstance(default.arg, compat.string_types):
default = default.arg
else:
@@ -654,6 +664,21 @@ def _render_server_default(default, autogen_context, repr_=True):
return default
+def _render_computed(computed, autogen_context):
+ text = _render_potential_expr(
+ computed.sqltext, autogen_context, wrap_in_text=False
+ )
+
+ kwargs = {}
+ if computed.persisted is not None:
+ kwargs["persisted"] = computed.persisted
+ return "%(prefix)sComputed(%(text)s, %(kwargs)s)" % {
+ "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
+ "text": text,
+ "kwargs": (", ".join("%s=%s" % pair for pair in kwargs.items())),
+ }
+
+
def _repr_type(type_, autogen_context):
rendered = _user_defined_render("type", type_, autogen_context)
if rendered is not False: