datatypes now support a setting of zero for "precision"; previously
a zero would be ignored. Pull request courtesy Ionuț Ciocîrlan.
+ .. change:: 3861
+ :tags: bug, engine
+ :tickets: 3861
+
+ The "extend_existing" option of :class:`.Table` reflection would
+ cause indexes and constraints to be doubled up in the case that the parameter
+ were used with :meth:`.MetaData.reflect` (as the automap extension does)
+ due to tables being reflected both within the foreign key path as well
+ as directly. A new de-duplicating set is passed through within the
+ :meth:`.MetaData.reflect` sequence to prevent double reflection in this
+ way.
+
.. change:: 3859
:tags: bug, sql
:tickets: 3859
return sqltypes.adapt_type(typeobj, self.colspecs)
def reflecttable(
- self, connection, table, include_columns, exclude_columns):
+ self, connection, table, include_columns, exclude_columns, **opts):
insp = reflection.Inspector.from_engine(connection)
- return insp.reflecttable(table, include_columns, exclude_columns)
+ return insp.reflecttable(
+ table, include_columns, exclude_columns, **opts)
def get_pk_constraint(self, conn, table_name, schema=None, **kw):
"""Compatibility method, adapts the result of get_primary_keys()
return self.dialect.get_check_constraints(
self.bind, table_name, schema, info_cache=self.info_cache, **kw)
- def reflecttable(self, table, include_columns, exclude_columns=()):
+ def reflecttable(self, table, include_columns, exclude_columns=(),
+ _extend_on=None):
"""Given a Table object, load its internal constructs based on
introspection.
in the reflection process. If ``None``, all columns are reflected.
"""
+
+ if _extend_on is not None:
+ if table in _extend_on:
+ return
+ else:
+ _extend_on.add(table)
+
dialect = self.bind.dialect
schema = self.bind.schema_for_object(table)
self._reflect_fk(
table_name, schema, table, cols_by_orig_name,
- exclude_columns, reflection_options)
+ exclude_columns, _extend_on, reflection_options)
self._reflect_indexes(
table_name, schema, table, cols_by_orig_name,
def _reflect_fk(
self, table_name, schema, table, cols_by_orig_name,
- exclude_columns, reflection_options):
+ exclude_columns, _extend_on, reflection_options):
fkeys = self.get_foreign_keys(
table_name, schema, **table.dialect_kwargs)
for fkey_d in fkeys:
sa_schema.Table(referred_table, table.metadata,
autoload=True, schema=referred_schema,
autoload_with=self.bind,
+ _extend_on=_extend_on,
**reflection_options
)
for column in referred_columns:
sa_schema.Table(referred_table, table.metadata, autoload=True,
autoload_with=self.bind,
schema=sa_schema.BLANK_SCHEMA,
+ _extend_on=_extend_on,
**reflection_options
)
for column in referred_columns:
autoload = kwargs.pop('autoload', autoload_with is not None)
# this argument is only used with _init_existing()
kwargs.pop('autoload_replace', True)
+ _extend_on = kwargs.pop("_extend_on", None)
+
include_columns = kwargs.pop('include_columns', None)
self.implicit_returning = kwargs.pop('implicit_returning', True)
# we do it after the table is in the singleton dictionary to support
# circular foreign keys
if autoload:
- self._autoload(metadata, autoload_with, include_columns)
+ self._autoload(
+ metadata, autoload_with,
+ include_columns, _extend_on=_extend_on)
# initialize all the column, etc. objects. done after reflection to
# allow user-overrides
self._init_items(*args)
def _autoload(self, metadata, autoload_with, include_columns,
- exclude_columns=()):
+ exclude_columns=(), _extend_on=None):
if autoload_with:
autoload_with.run_callable(
autoload_with.dialect.reflecttable,
- self, include_columns, exclude_columns
+ self, include_columns, exclude_columns,
+ _extend_on=_extend_on
)
else:
bind = _bind_or_error(
"metadata.bind=<someengine>")
bind.run_callable(
bind.dialect.reflecttable,
- self, include_columns, exclude_columns
+ self, include_columns, exclude_columns,
+ _extend_on=_extend_on
)
@property
autoload = kwargs.pop('autoload', autoload_with is not None)
autoload_replace = kwargs.pop('autoload_replace', True)
schema = kwargs.pop('schema', None)
+ _extend_on = kwargs.pop('_extend_on', None)
+
if schema and schema != self.schema:
raise exc.ArgumentError(
"Can't change schema of existing table from '%s' to '%s'",
if autoload:
if not autoload_replace:
+ # don't replace columns already present.
+ # we'd like to do this for constraints also however we don't
+ # have simple de-duping for unnamed constraints.
exclude_columns = [c.name for c in self.c]
else:
exclude_columns = ()
self._autoload(
self.metadata, autoload_with,
- include_columns, exclude_columns)
+ include_columns, exclude_columns, _extend_on=_extend_on)
self._extra_kwargs(**kwargs)
self._init_items(*args)
'autoload': True,
'autoload_with': conn,
'extend_existing': extend_existing,
- 'autoload_replace': autoload_replace
+ 'autoload_replace': autoload_replace,
+ '_extend_on': set()
}
reflect_opts.update(dialect_kwargs)
import unicodedata
import sqlalchemy as sa
from sqlalchemy import schema, inspect
-from sqlalchemy import MetaData, Integer, String
+from sqlalchemy import MetaData, Integer, String, Index, ForeignKey, \
+ UniqueConstraint
from sqlalchemy.testing import (
ComparesTables, engines, AssertsCompiledSQL,
fixtures, skip)
assert t4.c.z.type._type_affinity is String
assert t4.c.q is old_q
+ @testing.provide_metadata
+ def test_extend_existing_reflect_all_dont_dupe_index(self):
+ m = self.metadata
+ d = Table(
+ "d", m, Column('id', Integer, primary_key=True),
+ Column('foo', String(50)),
+ Column('bar', String(50)),
+ UniqueConstraint('bar')
+ )
+ Index("foo_idx", d.c.foo)
+ Table(
+ "b", m, Column('id', Integer, primary_key=True),
+ Column('aid', ForeignKey('d.id'))
+ )
+ m.create_all()
+
+ m2 = MetaData()
+ m2.reflect(testing.db, extend_existing=True)
+
+ eq_(
+ len([idx for idx in m2.tables['d'].indexes
+ if idx.name == 'foo_idx']),
+ 1
+ )
+ if testing.requires.\
+ unique_constraint_reflection_no_index_overlap.enabled:
+ eq_(
+ len([
+ const for const in m2.tables['d'].constraints
+ if isinstance(const, UniqueConstraint)]),
+ 1
+ )
+
@testing.emits_warning(r".*omitted columns")
@testing.provide_metadata
def test_include_columns_indexes(self):
"sqlite"
)
+ @property
+ def unique_constraint_reflection_no_index_overlap(self):
+ return self.unique_constraint_reflection + skip_if("mysql")
+
@property
def check_constraint_reflection(self):
return fails_on_everything_except(