- The rendering of a :class:~sqlalchemy.schema.ForeignKeyConstraint
will now ensure that the names of the source and target columns are the database-side name of each column, and not the value of the ``.key`` attribute as may be set only on the Python side. This is because Alembic generates the DDL for constraints as standalone objects without the need to actually refer to an in-Python :class:`~sqlalchemy.schema.Table` object, so there's no step that would resolve these Python-only key names to database column names. fixes #259
This commit is contained in:
parent
d12da7c187
commit
00dae5f77e
@ -543,12 +543,22 @@ def _fk_colspec(fk, metadata_schema):
|
||||
|
||||
"""
|
||||
colspec = fk._get_colspec()
|
||||
if metadata_schema is not None and colspec.count(".") == 1:
|
||||
# need to render schema breaking up tokens by hand, since the
|
||||
# ForeignKeyConstraint here may not actually have a remote
|
||||
# Table present
|
||||
# no schema in the colspec, render it
|
||||
colspec = "%s.%s" % (metadata_schema, colspec)
|
||||
tokens = colspec.split(".")
|
||||
tname, colname = tokens[-2:]
|
||||
|
||||
if metadata_schema is not None and len(tokens) == 2:
|
||||
table_fullname = "%s.%s" % (metadata_schema, tname)
|
||||
else:
|
||||
table_fullname = ".".join(tokens[0:-1])
|
||||
|
||||
if fk.parent is not None and fk.parent.table is not None:
|
||||
# try to resolve the remote table and adjust for column.key
|
||||
parent_metadata = fk.parent.table.metadata
|
||||
if table_fullname in parent_metadata.tables:
|
||||
colname = _ident(parent_metadata.tables[table_fullname].c[colname].name)
|
||||
|
||||
colspec = "%s.%s" % (table_fullname, colname)
|
||||
|
||||
return colspec
|
||||
|
||||
|
||||
@ -577,7 +587,7 @@ def _render_foreign_key(constraint, autogen_context):
|
||||
"[%(refcols)s], %(args)s)" % {
|
||||
"prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
|
||||
"cols": ", ".join(
|
||||
"%r" % f.parent.key for f in constraint.elements),
|
||||
"%r" % _ident(f.parent.name) for f in constraint.elements),
|
||||
"refcols": ", ".join(repr(_fk_colspec(f, apply_metadata_schema))
|
||||
for f in constraint.elements),
|
||||
"args": ", ".join(
|
||||
|
13
docs/build/changelog.rst
vendored
13
docs/build/changelog.rst
vendored
@ -6,6 +6,19 @@ Changelog
|
||||
.. changelog::
|
||||
:version: 0.7.4
|
||||
|
||||
.. change::
|
||||
:tags: bug, autogenerate
|
||||
:tickets: 259
|
||||
|
||||
The rendering of a :class:`~sqlalchemy.schema.ForeignKeyConstraint`
|
||||
will now ensure that the names of the source and target columns are
|
||||
the database-side name of each column, and not the value of the
|
||||
``.key`` attribute as may be set only on the Python side.
|
||||
This is because Alembic generates the DDL for constraints
|
||||
as standalone objects without the need to actually refer to an in-Python
|
||||
:class:`~sqlalchemy.schema.Table` object, so there's no step that
|
||||
would resolve these Python-only key names to database column names.
|
||||
|
||||
.. change::
|
||||
:tags: bug, autogenerate
|
||||
:tickets: 260
|
||||
|
@ -19,6 +19,9 @@ from alembic.testing.mock import patch
|
||||
from alembic import autogenerate, util, compat
|
||||
from alembic.testing import eq_, eq_ignore_whitespace, config
|
||||
|
||||
from alembic.testing.fixtures import op_fixture
|
||||
from alembic import op # noqa
|
||||
import sqlalchemy as sa # noqa
|
||||
|
||||
py3k = sys.version_info >= (3, )
|
||||
|
||||
@ -266,6 +269,64 @@ unique=False, """
|
||||
"op.create_foreign_key('fk_a_id', 'b', 'a', ['a_id'], ['id'])"
|
||||
)
|
||||
|
||||
def test_add_fk_constraint_inline_colkeys(self):
|
||||
m = MetaData()
|
||||
Table('a', m, Column('id', Integer, key='aid', primary_key=True))
|
||||
b = Table(
|
||||
'b', m,
|
||||
Column('a_id', Integer, ForeignKey('a.aid'), key='baid'))
|
||||
|
||||
py_code = autogenerate.render._add_table(b, self.autogen_context)
|
||||
|
||||
eq_ignore_whitespace(
|
||||
py_code,
|
||||
"op.create_table('b',"
|
||||
"sa.Column('a_id', sa.Integer(), nullable=True),"
|
||||
"sa.ForeignKeyConstraint(['a_id'], ['a.id'], ))"
|
||||
)
|
||||
|
||||
context = op_fixture()
|
||||
eval(py_code)
|
||||
context.assert_(
|
||||
"CREATE TABLE b (a_id INTEGER, "
|
||||
"FOREIGN KEY(a_id) REFERENCES a (id))")
|
||||
|
||||
def test_add_fk_constraint_separate_colkeys(self):
|
||||
m = MetaData()
|
||||
Table('a', m, Column('id', Integer, key='aid', primary_key=True))
|
||||
b = Table('b', m, Column('a_id', Integer, key='baid'))
|
||||
fk = ForeignKeyConstraint(['baid'], ['a.aid'], name='fk_a_id')
|
||||
b.append_constraint(fk)
|
||||
|
||||
py_code = autogenerate.render._add_table(b, self.autogen_context)
|
||||
|
||||
eq_ignore_whitespace(
|
||||
py_code,
|
||||
"op.create_table('b',"
|
||||
"sa.Column('a_id', sa.Integer(), nullable=True),"
|
||||
"sa.ForeignKeyConstraint(['a_id'], ['a.id'], name='fk_a_id'))"
|
||||
)
|
||||
|
||||
context = op_fixture()
|
||||
eval(py_code)
|
||||
context.assert_(
|
||||
"CREATE TABLE b (a_id INTEGER, CONSTRAINT "
|
||||
"fk_a_id FOREIGN KEY(a_id) REFERENCES a (id))")
|
||||
|
||||
context = op_fixture()
|
||||
py_code = autogenerate.render._add_fk_constraint(
|
||||
fk, self.autogen_context)
|
||||
|
||||
eq_ignore_whitespace(
|
||||
autogenerate.render._add_fk_constraint(fk, self.autogen_context),
|
||||
"op.create_foreign_key('fk_a_id', 'b', 'a', ['a_id'], ['id'])"
|
||||
)
|
||||
|
||||
eval(py_code)
|
||||
context.assert_(
|
||||
"ALTER TABLE b ADD CONSTRAINT fk_a_id "
|
||||
"FOREIGN KEY(a_id) REFERENCES a (id)")
|
||||
|
||||
def test_add_fk_constraint_schema(self):
|
||||
m = MetaData()
|
||||
Table(
|
||||
|
Loading…
Reference in New Issue
Block a user