Remove submodule, just put Dependencies in ./libs
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
This module extends SQLAlchemy and provides additional DDL [#]_
|
||||
support.
|
||||
|
||||
.. [#] SQL Data Definition Language
|
||||
"""
|
||||
import re
|
||||
import warnings
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy import __version__ as _sa_version
|
||||
|
||||
warnings.simplefilter('always', DeprecationWarning)
|
||||
|
||||
_sa_version = tuple(int(re.match("\d+", x).group(0)) for x in _sa_version.split("."))
|
||||
SQLA_06 = _sa_version >= (0, 6)
|
||||
|
||||
del re
|
||||
del _sa_version
|
||||
|
||||
from migrate.changeset.schema import *
|
||||
from migrate.changeset.constraint import *
|
||||
|
||||
sqlalchemy.schema.Table.__bases__ += (ChangesetTable, )
|
||||
sqlalchemy.schema.Column.__bases__ += (ChangesetColumn, )
|
||||
sqlalchemy.schema.Index.__bases__ += (ChangesetIndex, )
|
||||
|
||||
sqlalchemy.schema.DefaultClause.__bases__ += (ChangesetDefaultClause, )
|
||||
@@ -0,0 +1,358 @@
|
||||
"""
|
||||
Extensions to SQLAlchemy for altering existing tables.
|
||||
|
||||
At the moment, this isn't so much based off of ANSI as much as
|
||||
things that just happen to work with multiple databases.
|
||||
"""
|
||||
import StringIO
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.schema import SchemaVisitor
|
||||
from sqlalchemy.engine.default import DefaultDialect
|
||||
from sqlalchemy.sql import ClauseElement
|
||||
from sqlalchemy.schema import (ForeignKeyConstraint,
|
||||
PrimaryKeyConstraint,
|
||||
CheckConstraint,
|
||||
UniqueConstraint,
|
||||
Index)
|
||||
|
||||
from migrate import exceptions
|
||||
from migrate.changeset import constraint, SQLA_06
|
||||
|
||||
if not SQLA_06:
|
||||
from sqlalchemy.sql.compiler import SchemaGenerator, SchemaDropper
|
||||
else:
|
||||
from sqlalchemy.schema import AddConstraint, DropConstraint
|
||||
from sqlalchemy.sql.compiler import DDLCompiler
|
||||
SchemaGenerator = SchemaDropper = DDLCompiler
|
||||
|
||||
|
||||
class AlterTableVisitor(SchemaVisitor):
|
||||
"""Common operations for ``ALTER TABLE`` statements."""
|
||||
|
||||
if SQLA_06:
|
||||
# engine.Compiler looks for .statement
|
||||
# when it spawns off a new compiler
|
||||
statement = ClauseElement()
|
||||
|
||||
def append(self, s):
|
||||
"""Append content to the SchemaIterator's query buffer."""
|
||||
|
||||
self.buffer.write(s)
|
||||
|
||||
def execute(self):
|
||||
"""Execute the contents of the SchemaIterator's buffer."""
|
||||
try:
|
||||
return self.connection.execute(self.buffer.getvalue())
|
||||
finally:
|
||||
self.buffer.truncate(0)
|
||||
|
||||
def __init__(self, dialect, connection, **kw):
|
||||
self.connection = connection
|
||||
self.buffer = StringIO.StringIO()
|
||||
self.preparer = dialect.identifier_preparer
|
||||
self.dialect = dialect
|
||||
|
||||
def traverse_single(self, elem):
|
||||
ret = super(AlterTableVisitor, self).traverse_single(elem)
|
||||
if ret:
|
||||
# adapt to 0.6 which uses a string-returning
|
||||
# object
|
||||
self.append(" %s" % ret)
|
||||
|
||||
def _to_table(self, param):
|
||||
"""Returns the table object for the given param object."""
|
||||
if isinstance(param, (sa.Column, sa.Index, sa.schema.Constraint)):
|
||||
ret = param.table
|
||||
else:
|
||||
ret = param
|
||||
return ret
|
||||
|
||||
def start_alter_table(self, param):
|
||||
"""Returns the start of an ``ALTER TABLE`` SQL-Statement.
|
||||
|
||||
Use the param object to determine the table name and use it
|
||||
for building the SQL statement.
|
||||
|
||||
:param param: object to determine the table from
|
||||
:type param: :class:`sqlalchemy.Column`, :class:`sqlalchemy.Index`,
|
||||
:class:`sqlalchemy.schema.Constraint`, :class:`sqlalchemy.Table`,
|
||||
or string (table name)
|
||||
"""
|
||||
table = self._to_table(param)
|
||||
self.append('\nALTER TABLE %s ' % self.preparer.format_table(table))
|
||||
return table
|
||||
|
||||
|
||||
class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
|
||||
"""Extends ansisql generator for column creation (alter table add col)"""
|
||||
|
||||
def visit_column(self, column):
|
||||
"""Create a column (table already exists).
|
||||
|
||||
:param column: column object
|
||||
:type column: :class:`sqlalchemy.Column` instance
|
||||
"""
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
table = self.start_alter_table(column)
|
||||
self.append("ADD ")
|
||||
self.append(self.get_column_specification(column))
|
||||
|
||||
for cons in column.constraints:
|
||||
self.traverse_single(cons)
|
||||
self.execute()
|
||||
|
||||
# ALTER TABLE STATEMENTS
|
||||
|
||||
# add indexes and unique constraints
|
||||
if column.index_name:
|
||||
Index(column.index_name,column).create()
|
||||
elif column.unique_name:
|
||||
constraint.UniqueConstraint(column,
|
||||
name=column.unique_name).create()
|
||||
|
||||
# SA bounds FK constraints to table, add manually
|
||||
for fk in column.foreign_keys:
|
||||
self.add_foreignkey(fk.constraint)
|
||||
|
||||
# add primary key constraint if needed
|
||||
if column.primary_key_name:
|
||||
cons = constraint.PrimaryKeyConstraint(column,
|
||||
name=column.primary_key_name)
|
||||
cons.create()
|
||||
|
||||
if SQLA_06:
|
||||
def add_foreignkey(self, fk):
|
||||
self.connection.execute(AddConstraint(fk))
|
||||
|
||||
class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
|
||||
"""Extends ANSI SQL dropper for column dropping (``ALTER TABLE
|
||||
DROP COLUMN``).
|
||||
"""
|
||||
|
||||
def visit_column(self, column):
|
||||
"""Drop a column from its table.
|
||||
|
||||
:param column: the column object
|
||||
:type column: :class:`sqlalchemy.Column`
|
||||
"""
|
||||
table = self.start_alter_table(column)
|
||||
self.append('DROP COLUMN %s' % self.preparer.format_column(column))
|
||||
self.execute()
|
||||
|
||||
|
||||
class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
|
||||
"""Manages changes to existing schema elements.
|
||||
|
||||
Note that columns are schema elements; ``ALTER TABLE ADD COLUMN``
|
||||
is in SchemaGenerator.
|
||||
|
||||
All items may be renamed. Columns can also have many of their properties -
|
||||
type, for example - changed.
|
||||
|
||||
Each function is passed a tuple, containing (object, name); where
|
||||
object is a type of object you'd expect for that function
|
||||
(ie. table for visit_table) and name is the object's new
|
||||
name. NONE means the name is unchanged.
|
||||
"""
|
||||
|
||||
def visit_table(self, table):
|
||||
"""Rename a table. Other ops aren't supported."""
|
||||
self.start_alter_table(table)
|
||||
self.append("RENAME TO %s" % self.preparer.quote(table.new_name,
|
||||
table.quote))
|
||||
self.execute()
|
||||
|
||||
def visit_index(self, index):
|
||||
"""Rename an index"""
|
||||
if hasattr(self, '_validate_identifier'):
|
||||
# SA <= 0.6.3
|
||||
self.append("ALTER INDEX %s RENAME TO %s" % (
|
||||
self.preparer.quote(
|
||||
self._validate_identifier(
|
||||
index.name, True), index.quote),
|
||||
self.preparer.quote(
|
||||
self._validate_identifier(
|
||||
index.new_name, True), index.quote)))
|
||||
else:
|
||||
# SA >= 0.6.5
|
||||
self.append("ALTER INDEX %s RENAME TO %s" % (
|
||||
self.preparer.quote(
|
||||
self._index_identifier(
|
||||
index.name), index.quote),
|
||||
self.preparer.quote(
|
||||
self._index_identifier(
|
||||
index.new_name), index.quote)))
|
||||
self.execute()
|
||||
|
||||
def visit_column(self, delta):
|
||||
"""Rename/change a column."""
|
||||
# ALTER COLUMN is implemented as several ALTER statements
|
||||
keys = delta.keys()
|
||||
if 'type' in keys:
|
||||
self._run_subvisit(delta, self._visit_column_type)
|
||||
if 'nullable' in keys:
|
||||
self._run_subvisit(delta, self._visit_column_nullable)
|
||||
if 'server_default' in keys:
|
||||
# Skip 'default': only handle server-side defaults, others
|
||||
# are managed by the app, not the db.
|
||||
self._run_subvisit(delta, self._visit_column_default)
|
||||
if 'name' in keys:
|
||||
self._run_subvisit(delta, self._visit_column_name, start_alter=False)
|
||||
|
||||
def _run_subvisit(self, delta, func, start_alter=True):
|
||||
"""Runs visit method based on what needs to be changed on column"""
|
||||
table = self._to_table(delta.table)
|
||||
col_name = delta.current_name
|
||||
if start_alter:
|
||||
self.start_alter_column(table, col_name)
|
||||
ret = func(table, delta.result_column, delta)
|
||||
self.execute()
|
||||
|
||||
def start_alter_column(self, table, col_name):
|
||||
"""Starts ALTER COLUMN"""
|
||||
self.start_alter_table(table)
|
||||
self.append("ALTER COLUMN %s " % self.preparer.quote(col_name, table.quote))
|
||||
|
||||
def _visit_column_nullable(self, table, column, delta):
|
||||
nullable = delta['nullable']
|
||||
if nullable:
|
||||
self.append("DROP NOT NULL")
|
||||
else:
|
||||
self.append("SET NOT NULL")
|
||||
|
||||
def _visit_column_default(self, table, column, delta):
|
||||
default_text = self.get_column_default_string(column)
|
||||
if default_text is not None:
|
||||
self.append("SET DEFAULT %s" % default_text)
|
||||
else:
|
||||
self.append("DROP DEFAULT")
|
||||
|
||||
def _visit_column_type(self, table, column, delta):
|
||||
type_ = delta['type']
|
||||
if SQLA_06:
|
||||
type_text = str(type_.compile(dialect=self.dialect))
|
||||
else:
|
||||
type_text = type_.dialect_impl(self.dialect).get_col_spec()
|
||||
self.append("TYPE %s" % type_text)
|
||||
|
||||
def _visit_column_name(self, table, column, delta):
|
||||
self.start_alter_table(table)
|
||||
col_name = self.preparer.quote(delta.current_name, table.quote)
|
||||
new_name = self.preparer.format_column(delta.result_column)
|
||||
self.append('RENAME COLUMN %s TO %s' % (col_name, new_name))
|
||||
|
||||
|
||||
class ANSIConstraintCommon(AlterTableVisitor):
|
||||
"""
|
||||
Migrate's constraints require a separate creation function from
|
||||
SA's: Migrate's constraints are created independently of a table;
|
||||
SA's are created at the same time as the table.
|
||||
"""
|
||||
|
||||
def get_constraint_name(self, cons):
|
||||
"""Gets a name for the given constraint.
|
||||
|
||||
If the name is already set it will be used otherwise the
|
||||
constraint's :meth:`autoname <migrate.changeset.constraint.ConstraintChangeset.autoname>`
|
||||
method is used.
|
||||
|
||||
:param cons: constraint object
|
||||
"""
|
||||
if cons.name is not None:
|
||||
ret = cons.name
|
||||
else:
|
||||
ret = cons.name = cons.autoname()
|
||||
return self.preparer.quote(ret, cons.quote)
|
||||
|
||||
def visit_migrate_primary_key_constraint(self, *p, **k):
|
||||
self._visit_constraint(*p, **k)
|
||||
|
||||
def visit_migrate_foreign_key_constraint(self, *p, **k):
|
||||
self._visit_constraint(*p, **k)
|
||||
|
||||
def visit_migrate_check_constraint(self, *p, **k):
|
||||
self._visit_constraint(*p, **k)
|
||||
|
||||
def visit_migrate_unique_constraint(self, *p, **k):
|
||||
self._visit_constraint(*p, **k)
|
||||
|
||||
if SQLA_06:
|
||||
class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
|
||||
def _visit_constraint(self, constraint):
|
||||
constraint.name = self.get_constraint_name(constraint)
|
||||
self.append(self.process(AddConstraint(constraint)))
|
||||
self.execute()
|
||||
|
||||
class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
|
||||
def _visit_constraint(self, constraint):
|
||||
constraint.name = self.get_constraint_name(constraint)
|
||||
self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
|
||||
self.execute()
|
||||
|
||||
else:
|
||||
class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
|
||||
|
||||
def get_constraint_specification(self, cons, **kwargs):
|
||||
"""Constaint SQL generators.
|
||||
|
||||
We cannot use SA visitors because they append comma.
|
||||
"""
|
||||
|
||||
if isinstance(cons, PrimaryKeyConstraint):
|
||||
if cons.name is not None:
|
||||
self.append("CONSTRAINT %s " % self.preparer.format_constraint(cons))
|
||||
self.append("PRIMARY KEY ")
|
||||
self.append("(%s)" % ', '.join(self.preparer.quote(c.name, c.quote)
|
||||
for c in cons))
|
||||
self.define_constraint_deferrability(cons)
|
||||
elif isinstance(cons, ForeignKeyConstraint):
|
||||
self.define_foreign_key(cons)
|
||||
elif isinstance(cons, CheckConstraint):
|
||||
if cons.name is not None:
|
||||
self.append("CONSTRAINT %s " %
|
||||
self.preparer.format_constraint(cons))
|
||||
self.append("CHECK (%s)" % cons.sqltext)
|
||||
self.define_constraint_deferrability(cons)
|
||||
elif isinstance(cons, UniqueConstraint):
|
||||
if cons.name is not None:
|
||||
self.append("CONSTRAINT %s " %
|
||||
self.preparer.format_constraint(cons))
|
||||
self.append("UNIQUE (%s)" % \
|
||||
(', '.join(self.preparer.quote(c.name, c.quote) for c in cons)))
|
||||
self.define_constraint_deferrability(cons)
|
||||
else:
|
||||
raise exceptions.InvalidConstraintError(cons)
|
||||
|
||||
def _visit_constraint(self, constraint):
|
||||
|
||||
table = self.start_alter_table(constraint)
|
||||
constraint.name = self.get_constraint_name(constraint)
|
||||
self.append("ADD ")
|
||||
self.get_constraint_specification(constraint)
|
||||
self.execute()
|
||||
|
||||
|
||||
class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
|
||||
|
||||
def _visit_constraint(self, constraint):
|
||||
self.start_alter_table(constraint)
|
||||
self.append("DROP CONSTRAINT ")
|
||||
constraint.name = self.get_constraint_name(constraint)
|
||||
self.append(self.preparer.format_constraint(constraint))
|
||||
if constraint.cascade:
|
||||
self.cascade_constraint(constraint)
|
||||
self.execute()
|
||||
|
||||
def cascade_constraint(self, constraint):
|
||||
self.append(" CASCADE")
|
||||
|
||||
|
||||
class ANSIDialect(DefaultDialect):
|
||||
columngenerator = ANSIColumnGenerator
|
||||
columndropper = ANSIColumnDropper
|
||||
schemachanger = ANSISchemaChanger
|
||||
constraintgenerator = ANSIConstraintGenerator
|
||||
constraintdropper = ANSIConstraintDropper
|
||||
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
This module defines standalone schema constraint classes.
|
||||
"""
|
||||
from sqlalchemy import schema
|
||||
|
||||
from migrate.exceptions import *
|
||||
from migrate.changeset import SQLA_06
|
||||
|
||||
class ConstraintChangeset(object):
|
||||
"""Base class for Constraint classes."""
|
||||
|
||||
def _normalize_columns(self, cols, table_name=False):
|
||||
"""Given: column objects or names; return col names and
|
||||
(maybe) a table"""
|
||||
colnames = []
|
||||
table = None
|
||||
for col in cols:
|
||||
if isinstance(col, schema.Column):
|
||||
if col.table is not None and table is None:
|
||||
table = col.table
|
||||
if table_name:
|
||||
col = '.'.join((col.table.name, col.name))
|
||||
else:
|
||||
col = col.name
|
||||
colnames.append(col)
|
||||
return colnames, table
|
||||
|
||||
def __do_imports(self, visitor_name, *a, **kw):
|
||||
engine = kw.pop('engine', self.table.bind)
|
||||
from migrate.changeset.databases.visitor import (get_engine_visitor,
|
||||
run_single_visitor)
|
||||
visitorcallable = get_engine_visitor(engine, visitor_name)
|
||||
run_single_visitor(engine, visitorcallable, self, *a, **kw)
|
||||
|
||||
def create(self, *a, **kw):
|
||||
"""Create the constraint in the database.
|
||||
|
||||
:param engine: the database engine to use. If this is \
|
||||
:keyword:`None` the instance's engine will be used
|
||||
:type engine: :class:`sqlalchemy.engine.base.Engine`
|
||||
:param connection: reuse connection istead of creating new one.
|
||||
:type connection: :class:`sqlalchemy.engine.base.Connection` instance
|
||||
"""
|
||||
# TODO: set the parent here instead of in __init__
|
||||
self.__do_imports('constraintgenerator', *a, **kw)
|
||||
|
||||
def drop(self, *a, **kw):
|
||||
"""Drop the constraint from the database.
|
||||
|
||||
:param engine: the database engine to use. If this is
|
||||
:keyword:`None` the instance's engine will be used
|
||||
:param cascade: Issue CASCADE drop if database supports it
|
||||
:type engine: :class:`sqlalchemy.engine.base.Engine`
|
||||
:type cascade: bool
|
||||
:param connection: reuse connection istead of creating new one.
|
||||
:type connection: :class:`sqlalchemy.engine.base.Connection` instance
|
||||
:returns: Instance with cleared columns
|
||||
"""
|
||||
self.cascade = kw.pop('cascade', False)
|
||||
self.__do_imports('constraintdropper', *a, **kw)
|
||||
# the spirit of Constraint objects is that they
|
||||
# are immutable (just like in a DB. they're only ADDed
|
||||
# or DROPped).
|
||||
#self.columns.clear()
|
||||
return self
|
||||
|
||||
|
||||
class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint):
|
||||
"""Construct PrimaryKeyConstraint
|
||||
|
||||
Migrate's additional parameters:
|
||||
|
||||
:param cols: Columns in constraint.
|
||||
:param table: If columns are passed as strings, this kw is required
|
||||
:type table: Table instance
|
||||
:type cols: strings or Column instances
|
||||
"""
|
||||
|
||||
__migrate_visit_name__ = 'migrate_primary_key_constraint'
|
||||
|
||||
def __init__(self, *cols, **kwargs):
|
||||
colnames, table = self._normalize_columns(cols)
|
||||
table = kwargs.pop('table', table)
|
||||
super(PrimaryKeyConstraint, self).__init__(*colnames, **kwargs)
|
||||
if table is not None:
|
||||
self._set_parent(table)
|
||||
|
||||
|
||||
def autoname(self):
|
||||
"""Mimic the database's automatic constraint names"""
|
||||
return "%s_pkey" % self.table.name
|
||||
|
||||
|
||||
class ForeignKeyConstraint(ConstraintChangeset, schema.ForeignKeyConstraint):
|
||||
"""Construct ForeignKeyConstraint
|
||||
|
||||
Migrate's additional parameters:
|
||||
|
||||
:param columns: Columns in constraint
|
||||
:param refcolumns: Columns that this FK reffers to in another table.
|
||||
:param table: If columns are passed as strings, this kw is required
|
||||
:type table: Table instance
|
||||
:type columns: list of strings or Column instances
|
||||
:type refcolumns: list of strings or Column instances
|
||||
"""
|
||||
|
||||
__migrate_visit_name__ = 'migrate_foreign_key_constraint'
|
||||
|
||||
def __init__(self, columns, refcolumns, *args, **kwargs):
|
||||
colnames, table = self._normalize_columns(columns)
|
||||
table = kwargs.pop('table', table)
|
||||
refcolnames, reftable = self._normalize_columns(refcolumns,
|
||||
table_name=True)
|
||||
super(ForeignKeyConstraint, self).__init__(colnames, refcolnames, *args,
|
||||
**kwargs)
|
||||
if table is not None:
|
||||
self._set_parent(table)
|
||||
|
||||
@property
|
||||
def referenced(self):
|
||||
return [e.column for e in self.elements]
|
||||
|
||||
@property
|
||||
def reftable(self):
|
||||
return self.referenced[0].table
|
||||
|
||||
def autoname(self):
|
||||
"""Mimic the database's automatic constraint names"""
|
||||
if hasattr(self.columns, 'keys'):
|
||||
# SA <= 0.5
|
||||
firstcol = self.columns[self.columns.keys()[0]]
|
||||
ret = "%(table)s_%(firstcolumn)s_fkey" % dict(
|
||||
table=firstcol.table.name,
|
||||
firstcolumn=firstcol.name,)
|
||||
else:
|
||||
# SA >= 0.6
|
||||
ret = "%(table)s_%(firstcolumn)s_fkey" % dict(
|
||||
table=self.table.name,
|
||||
firstcolumn=self.columns[0],)
|
||||
return ret
|
||||
|
||||
|
||||
class CheckConstraint(ConstraintChangeset, schema.CheckConstraint):
|
||||
"""Construct CheckConstraint
|
||||
|
||||
Migrate's additional parameters:
|
||||
|
||||
:param sqltext: Plain SQL text to check condition
|
||||
:param columns: If not name is applied, you must supply this kw\
|
||||
to autoname constraint
|
||||
:param table: If columns are passed as strings, this kw is required
|
||||
:type table: Table instance
|
||||
:type columns: list of Columns instances
|
||||
:type sqltext: string
|
||||
"""
|
||||
|
||||
__migrate_visit_name__ = 'migrate_check_constraint'
|
||||
|
||||
def __init__(self, sqltext, *args, **kwargs):
|
||||
cols = kwargs.pop('columns', [])
|
||||
if not cols and not kwargs.get('name', False):
|
||||
raise InvalidConstraintError('You must either set "name"'
|
||||
'parameter or "columns" to autogenarate it.')
|
||||
colnames, table = self._normalize_columns(cols)
|
||||
table = kwargs.pop('table', table)
|
||||
schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
|
||||
if table is not None:
|
||||
if not SQLA_06:
|
||||
self.table = table
|
||||
self._set_parent(table)
|
||||
self.colnames = colnames
|
||||
|
||||
def autoname(self):
|
||||
return "%(table)s_%(cols)s_check" % \
|
||||
dict(table=self.table.name, cols="_".join(self.colnames))
|
||||
|
||||
|
||||
class UniqueConstraint(ConstraintChangeset, schema.UniqueConstraint):
|
||||
"""Construct UniqueConstraint
|
||||
|
||||
Migrate's additional parameters:
|
||||
|
||||
:param cols: Columns in constraint.
|
||||
:param table: If columns are passed as strings, this kw is required
|
||||
:type table: Table instance
|
||||
:type cols: strings or Column instances
|
||||
|
||||
.. versionadded:: 0.6.0
|
||||
"""
|
||||
|
||||
__migrate_visit_name__ = 'migrate_unique_constraint'
|
||||
|
||||
def __init__(self, *cols, **kwargs):
|
||||
self.colnames, table = self._normalize_columns(cols)
|
||||
table = kwargs.pop('table', table)
|
||||
super(UniqueConstraint, self).__init__(*self.colnames, **kwargs)
|
||||
if table is not None:
|
||||
self._set_parent(table)
|
||||
|
||||
def autoname(self):
|
||||
"""Mimic the database's automatic constraint names"""
|
||||
return "%s_%s_key" % (self.table.name, self.colnames[0])
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
This module contains database dialect specific changeset
|
||||
implementations.
|
||||
"""
|
||||
__all__ = [
|
||||
'postgres',
|
||||
'sqlite',
|
||||
'mysql',
|
||||
'oracle',
|
||||
]
|
||||
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Firebird database specific implementations of changeset classes.
|
||||
"""
|
||||
from sqlalchemy.databases import firebird as sa_base
|
||||
from sqlalchemy.schema import PrimaryKeyConstraint
|
||||
from migrate import exceptions
|
||||
from migrate.changeset import ansisql, SQLA_06
|
||||
|
||||
|
||||
if SQLA_06:
|
||||
FBSchemaGenerator = sa_base.FBDDLCompiler
|
||||
else:
|
||||
FBSchemaGenerator = sa_base.FBSchemaGenerator
|
||||
|
||||
class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator):
|
||||
"""Firebird column generator implementation."""
|
||||
|
||||
|
||||
class FBColumnDropper(ansisql.ANSIColumnDropper):
|
||||
"""Firebird column dropper implementation."""
|
||||
|
||||
def visit_column(self, column):
|
||||
"""Firebird supports 'DROP col' instead of 'DROP COLUMN col' syntax
|
||||
|
||||
Drop primary key and unique constraints if dropped column is referencing it."""
|
||||
if column.primary_key:
|
||||
if column.table.primary_key.columns.contains_column(column):
|
||||
column.table.primary_key.drop()
|
||||
# TODO: recreate primary key if it references more than this column
|
||||
|
||||
for index in column.table.indexes:
|
||||
# "column in index.columns" causes problems as all
|
||||
# column objects compare equal and return a SQL expression
|
||||
if column.name in [col.name for col in index.columns]:
|
||||
index.drop()
|
||||
# TODO: recreate index if it references more than this column
|
||||
|
||||
for cons in column.table.constraints:
|
||||
if isinstance(cons,PrimaryKeyConstraint):
|
||||
# will be deleted only when the column its on
|
||||
# is deleted!
|
||||
continue
|
||||
|
||||
if SQLA_06:
|
||||
should_drop = column.name in cons.columns
|
||||
else:
|
||||
should_drop = cons.contains_column(column) and cons.name
|
||||
if should_drop:
|
||||
self.start_alter_table(column)
|
||||
self.append("DROP CONSTRAINT ")
|
||||
self.append(self.preparer.format_constraint(cons))
|
||||
self.execute()
|
||||
# TODO: recreate unique constraint if it refenrences more than this column
|
||||
|
||||
self.start_alter_table(column)
|
||||
self.append('DROP %s' % self.preparer.format_column(column))
|
||||
self.execute()
|
||||
|
||||
|
||||
class FBSchemaChanger(ansisql.ANSISchemaChanger):
|
||||
"""Firebird schema changer implementation."""
|
||||
|
||||
def visit_table(self, table):
|
||||
"""Rename table not supported"""
|
||||
raise exceptions.NotSupportedError(
|
||||
"Firebird does not support renaming tables.")
|
||||
|
||||
def _visit_column_name(self, table, column, delta):
|
||||
self.start_alter_table(table)
|
||||
col_name = self.preparer.quote(delta.current_name, table.quote)
|
||||
new_name = self.preparer.format_column(delta.result_column)
|
||||
self.append('ALTER COLUMN %s TO %s' % (col_name, new_name))
|
||||
|
||||
def _visit_column_nullable(self, table, column, delta):
|
||||
"""Changing NULL is not supported"""
|
||||
# TODO: http://www.firebirdfaq.org/faq103/
|
||||
raise exceptions.NotSupportedError(
|
||||
"Firebird does not support altering NULL bevahior.")
|
||||
|
||||
|
||||
class FBConstraintGenerator(ansisql.ANSIConstraintGenerator):
|
||||
"""Firebird constraint generator implementation."""
|
||||
|
||||
|
||||
class FBConstraintDropper(ansisql.ANSIConstraintDropper):
|
||||
"""Firebird constaint dropper implementation."""
|
||||
|
||||
def cascade_constraint(self, constraint):
|
||||
"""Cascading constraints is not supported"""
|
||||
raise exceptions.NotSupportedError(
|
||||
"Firebird does not support cascading constraints")
|
||||
|
||||
|
||||
class FBDialect(ansisql.ANSIDialect):
|
||||
columngenerator = FBColumnGenerator
|
||||
columndropper = FBColumnDropper
|
||||
schemachanger = FBSchemaChanger
|
||||
constraintgenerator = FBConstraintGenerator
|
||||
constraintdropper = FBConstraintDropper
|
||||
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
MySQL database specific implementations of changeset classes.
|
||||
"""
|
||||
|
||||
from sqlalchemy.databases import mysql as sa_base
|
||||
from sqlalchemy import types as sqltypes
|
||||
|
||||
from migrate import exceptions
|
||||
from migrate.changeset import ansisql, SQLA_06
|
||||
|
||||
|
||||
if not SQLA_06:
|
||||
MySQLSchemaGenerator = sa_base.MySQLSchemaGenerator
|
||||
else:
|
||||
MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
|
||||
|
||||
class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLColumnDropper(ansisql.ANSIColumnDropper):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLSchemaChanger(MySQLSchemaGenerator, ansisql.ANSISchemaChanger):
|
||||
|
||||
def visit_column(self, delta):
|
||||
table = delta.table
|
||||
colspec = self.get_column_specification(delta.result_column)
|
||||
if delta.result_column.autoincrement:
|
||||
primary_keys = [c for c in table.primary_key.columns
|
||||
if (c.autoincrement and
|
||||
isinstance(c.type, sqltypes.Integer) and
|
||||
not c.foreign_keys)]
|
||||
|
||||
if primary_keys:
|
||||
first = primary_keys.pop(0)
|
||||
if first.name == delta.current_name:
|
||||
colspec += " AUTO_INCREMENT"
|
||||
old_col_name = self.preparer.quote(delta.current_name, table.quote)
|
||||
|
||||
self.start_alter_table(table)
|
||||
|
||||
self.append("CHANGE COLUMN %s " % old_col_name)
|
||||
self.append(colspec)
|
||||
self.execute()
|
||||
|
||||
def visit_index(self, param):
|
||||
# If MySQL can do this, I can't find how
|
||||
raise exceptions.NotSupportedError("MySQL cannot rename indexes")
|
||||
|
||||
|
||||
class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator):
|
||||
pass
|
||||
|
||||
if SQLA_06:
|
||||
class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper):
|
||||
def visit_migrate_check_constraint(self, *p, **k):
|
||||
raise exceptions.NotSupportedError("MySQL does not support CHECK"
|
||||
" constraints, use triggers instead.")
|
||||
|
||||
else:
|
||||
class MySQLConstraintDropper(ansisql.ANSIConstraintDropper):
|
||||
|
||||
def visit_migrate_primary_key_constraint(self, constraint):
|
||||
self.start_alter_table(constraint)
|
||||
self.append("DROP PRIMARY KEY")
|
||||
self.execute()
|
||||
|
||||
def visit_migrate_foreign_key_constraint(self, constraint):
|
||||
self.start_alter_table(constraint)
|
||||
self.append("DROP FOREIGN KEY ")
|
||||
constraint.name = self.get_constraint_name(constraint)
|
||||
self.append(self.preparer.format_constraint(constraint))
|
||||
self.execute()
|
||||
|
||||
def visit_migrate_check_constraint(self, *p, **k):
|
||||
raise exceptions.NotSupportedError("MySQL does not support CHECK"
|
||||
" constraints, use triggers instead.")
|
||||
|
||||
def visit_migrate_unique_constraint(self, constraint, *p, **k):
|
||||
self.start_alter_table(constraint)
|
||||
self.append('DROP INDEX ')
|
||||
constraint.name = self.get_constraint_name(constraint)
|
||||
self.append(self.preparer.format_constraint(constraint))
|
||||
self.execute()
|
||||
|
||||
|
||||
class MySQLDialect(ansisql.ANSIDialect):
|
||||
columngenerator = MySQLColumnGenerator
|
||||
columndropper = MySQLColumnDropper
|
||||
schemachanger = MySQLSchemaChanger
|
||||
constraintgenerator = MySQLConstraintGenerator
|
||||
constraintdropper = MySQLConstraintDropper
|
||||
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
Oracle database specific implementations of changeset classes.
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.databases import oracle as sa_base
|
||||
|
||||
from migrate import exceptions
|
||||
from migrate.changeset import ansisql, SQLA_06
|
||||
|
||||
|
||||
if not SQLA_06:
|
||||
OracleSchemaGenerator = sa_base.OracleSchemaGenerator
|
||||
else:
|
||||
OracleSchemaGenerator = sa_base.OracleDDLCompiler
|
||||
|
||||
|
||||
class OracleColumnGenerator(OracleSchemaGenerator, ansisql.ANSIColumnGenerator):
|
||||
pass
|
||||
|
||||
|
||||
class OracleColumnDropper(ansisql.ANSIColumnDropper):
|
||||
pass
|
||||
|
||||
|
||||
class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger):
|
||||
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
# Ignore the NOT NULL generated
|
||||
override_nullable = kwargs.pop('override_nullable', None)
|
||||
if override_nullable:
|
||||
orig = column.nullable
|
||||
column.nullable = True
|
||||
ret = super(OracleSchemaChanger, self).get_column_specification(
|
||||
column, **kwargs)
|
||||
if override_nullable:
|
||||
column.nullable = orig
|
||||
return ret
|
||||
|
||||
def visit_column(self, delta):
|
||||
keys = delta.keys()
|
||||
|
||||
if 'name' in keys:
|
||||
self._run_subvisit(delta,
|
||||
self._visit_column_name,
|
||||
start_alter=False)
|
||||
|
||||
if len(set(('type', 'nullable', 'server_default')).intersection(keys)):
|
||||
self._run_subvisit(delta,
|
||||
self._visit_column_change,
|
||||
start_alter=False)
|
||||
|
||||
def _visit_column_change(self, table, column, delta):
|
||||
# Oracle cannot drop a default once created, but it can set it
|
||||
# to null. We'll do that if default=None
|
||||
# http://forums.oracle.com/forums/message.jspa?messageID=1273234#1273234
|
||||
dropdefault_hack = (column.server_default is None \
|
||||
and 'server_default' in delta.keys())
|
||||
# Oracle apparently doesn't like it when we say "not null" if
|
||||
# the column's already not null. Fudge it, so we don't need a
|
||||
# new function
|
||||
notnull_hack = ((not column.nullable) \
|
||||
and ('nullable' not in delta.keys()))
|
||||
# We need to specify NULL if we're removing a NOT NULL
|
||||
# constraint
|
||||
null_hack = (column.nullable and ('nullable' in delta.keys()))
|
||||
|
||||
if dropdefault_hack:
|
||||
column.server_default = sa.PassiveDefault(sa.sql.null())
|
||||
if notnull_hack:
|
||||
column.nullable = True
|
||||
colspec = self.get_column_specification(column,
|
||||
override_nullable=null_hack)
|
||||
if null_hack:
|
||||
colspec += ' NULL'
|
||||
if notnull_hack:
|
||||
column.nullable = False
|
||||
if dropdefault_hack:
|
||||
column.server_default = None
|
||||
|
||||
self.start_alter_table(table)
|
||||
self.append("MODIFY (")
|
||||
self.append(colspec)
|
||||
self.append(")")
|
||||
|
||||
|
||||
class OracleConstraintCommon(object):
|
||||
|
||||
def get_constraint_name(self, cons):
|
||||
# Oracle constraints can't guess their name like other DBs
|
||||
if not cons.name:
|
||||
raise exceptions.NotSupportedError(
|
||||
"Oracle constraint names must be explicitly stated")
|
||||
return cons.name
|
||||
|
||||
|
||||
class OracleConstraintGenerator(OracleConstraintCommon,
|
||||
ansisql.ANSIConstraintGenerator):
|
||||
pass
|
||||
|
||||
|
||||
class OracleConstraintDropper(OracleConstraintCommon,
|
||||
ansisql.ANSIConstraintDropper):
|
||||
pass
|
||||
|
||||
|
||||
class OracleDialect(ansisql.ANSIDialect):
|
||||
columngenerator = OracleColumnGenerator
|
||||
columndropper = OracleColumnDropper
|
||||
schemachanger = OracleSchemaChanger
|
||||
constraintgenerator = OracleConstraintGenerator
|
||||
constraintdropper = OracleConstraintDropper
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
`PostgreSQL`_ database specific implementations of changeset classes.
|
||||
|
||||
.. _`PostgreSQL`: http://www.postgresql.org/
|
||||
"""
|
||||
from migrate.changeset import ansisql, SQLA_06
|
||||
|
||||
if not SQLA_06:
|
||||
from sqlalchemy.databases import postgres as sa_base
|
||||
PGSchemaGenerator = sa_base.PGSchemaGenerator
|
||||
else:
|
||||
from sqlalchemy.databases import postgresql as sa_base
|
||||
PGSchemaGenerator = sa_base.PGDDLCompiler
|
||||
|
||||
|
||||
class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator):
|
||||
"""PostgreSQL column generator implementation."""
|
||||
pass
|
||||
|
||||
|
||||
class PGColumnDropper(ansisql.ANSIColumnDropper):
|
||||
"""PostgreSQL column dropper implementation."""
|
||||
pass
|
||||
|
||||
|
||||
class PGSchemaChanger(ansisql.ANSISchemaChanger):
|
||||
"""PostgreSQL schema changer implementation."""
|
||||
pass
|
||||
|
||||
|
||||
class PGConstraintGenerator(ansisql.ANSIConstraintGenerator):
|
||||
"""PostgreSQL constraint generator implementation."""
|
||||
pass
|
||||
|
||||
|
||||
class PGConstraintDropper(ansisql.ANSIConstraintDropper):
|
||||
"""PostgreSQL constaint dropper implementation."""
|
||||
pass
|
||||
|
||||
|
||||
class PGDialect(ansisql.ANSIDialect):
|
||||
columngenerator = PGColumnGenerator
|
||||
columndropper = PGColumnDropper
|
||||
schemachanger = PGSchemaChanger
|
||||
constraintgenerator = PGConstraintGenerator
|
||||
constraintdropper = PGConstraintDropper
|
||||
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
`SQLite`_ database specific implementations of changeset classes.
|
||||
|
||||
.. _`SQLite`: http://www.sqlite.org/
|
||||
"""
|
||||
from UserDict import DictMixin
|
||||
from copy import copy
|
||||
|
||||
from sqlalchemy.databases import sqlite as sa_base
|
||||
|
||||
from migrate import exceptions
|
||||
from migrate.changeset import ansisql, SQLA_06
|
||||
|
||||
|
||||
if not SQLA_06:
|
||||
SQLiteSchemaGenerator = sa_base.SQLiteSchemaGenerator
|
||||
else:
|
||||
SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
|
||||
|
||||
class SQLiteCommon(object):
|
||||
|
||||
def _not_supported(self, op):
|
||||
raise exceptions.NotSupportedError("SQLite does not support "
|
||||
"%s; see http://www.sqlite.org/lang_altertable.html" % op)
|
||||
|
||||
|
||||
class SQLiteHelper(SQLiteCommon):
|
||||
|
||||
def recreate_table(self,table,column=None,delta=None):
|
||||
table_name = self.preparer.format_table(table)
|
||||
|
||||
# we remove all indexes so as not to have
|
||||
# problems during copy and re-create
|
||||
for index in table.indexes:
|
||||
index.drop()
|
||||
|
||||
self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
|
||||
self.execute()
|
||||
|
||||
insertion_string = self._modify_table(table, column, delta)
|
||||
|
||||
table.create()
|
||||
self.append(insertion_string % {'table_name': table_name})
|
||||
self.execute()
|
||||
self.append('DROP TABLE migration_tmp')
|
||||
self.execute()
|
||||
|
||||
def visit_column(self, delta):
|
||||
if isinstance(delta, DictMixin):
|
||||
column = delta.result_column
|
||||
table = self._to_table(delta.table)
|
||||
else:
|
||||
column = delta
|
||||
table = self._to_table(column.table)
|
||||
self.recreate_table(table,column,delta)
|
||||
|
||||
class SQLiteColumnGenerator(SQLiteSchemaGenerator,
|
||||
ansisql.ANSIColumnGenerator,
|
||||
# at the end so we get the normal
|
||||
# visit_column by default
|
||||
SQLiteHelper,
|
||||
SQLiteCommon
|
||||
):
|
||||
"""SQLite ColumnGenerator"""
|
||||
|
||||
def _modify_table(self, table, column, delta):
|
||||
columns = ' ,'.join(map(
|
||||
self.preparer.format_column,
|
||||
[c for c in table.columns if c.name!=column.name]))
|
||||
return ('INSERT INTO %%(table_name)s (%(cols)s) '
|
||||
'SELECT %(cols)s from migration_tmp')%{'cols':columns}
|
||||
|
||||
def visit_column(self,column):
|
||||
if column.foreign_keys:
|
||||
SQLiteHelper.visit_column(self,column)
|
||||
else:
|
||||
super(SQLiteColumnGenerator,self).visit_column(column)
|
||||
|
||||
class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
|
||||
"""SQLite ColumnDropper"""
|
||||
|
||||
def _modify_table(self, table, column, delta):
|
||||
|
||||
columns = ' ,'.join(map(self.preparer.format_column, table.columns))
|
||||
return 'INSERT INTO %(table_name)s SELECT ' + columns + \
|
||||
' from migration_tmp'
|
||||
|
||||
def visit_column(self,column):
|
||||
# For SQLite, we *have* to remove the column here so the table
|
||||
# is re-created properly.
|
||||
column.remove_from_table(column.table,unset_table=False)
|
||||
super(SQLiteColumnDropper,self).visit_column(column)
|
||||
|
||||
|
||||
class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
|
||||
"""SQLite SchemaChanger"""
|
||||
|
||||
def _modify_table(self, table, column, delta):
|
||||
return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
|
||||
|
||||
def visit_index(self, index):
|
||||
"""Does not support ALTER INDEX"""
|
||||
self._not_supported('ALTER INDEX')
|
||||
|
||||
|
||||
class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon):
|
||||
|
||||
def visit_migrate_primary_key_constraint(self, constraint):
|
||||
tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )"
|
||||
cols = ', '.join(map(self.preparer.format_column, constraint.columns))
|
||||
tname = self.preparer.format_table(constraint.table)
|
||||
name = self.get_constraint_name(constraint)
|
||||
msg = tmpl % (name, tname, cols)
|
||||
self.append(msg)
|
||||
self.execute()
|
||||
|
||||
def _modify_table(self, table, column, delta):
|
||||
return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
|
||||
|
||||
def visit_migrate_foreign_key_constraint(self, *p, **k):
|
||||
self.recreate_table(p[0].table)
|
||||
|
||||
def visit_migrate_unique_constraint(self, *p, **k):
|
||||
self.recreate_table(p[0].table)
|
||||
|
||||
|
||||
class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
|
||||
SQLiteCommon,
|
||||
ansisql.ANSIConstraintCommon):
|
||||
|
||||
def visit_migrate_primary_key_constraint(self, constraint):
|
||||
tmpl = "DROP INDEX %s "
|
||||
name = self.get_constraint_name(constraint)
|
||||
msg = tmpl % (name)
|
||||
self.append(msg)
|
||||
self.execute()
|
||||
|
||||
def visit_migrate_foreign_key_constraint(self, *p, **k):
|
||||
self._not_supported('ALTER TABLE DROP CONSTRAINT')
|
||||
|
||||
def visit_migrate_check_constraint(self, *p, **k):
|
||||
self._not_supported('ALTER TABLE DROP CONSTRAINT')
|
||||
|
||||
def visit_migrate_unique_constraint(self, *p, **k):
|
||||
self._not_supported('ALTER TABLE DROP CONSTRAINT')
|
||||
|
||||
|
||||
# TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
|
||||
|
||||
class SQLiteDialect(ansisql.ANSIDialect):
|
||||
columngenerator = SQLiteColumnGenerator
|
||||
columndropper = SQLiteColumnDropper
|
||||
schemachanger = SQLiteSchemaChanger
|
||||
constraintgenerator = SQLiteConstraintGenerator
|
||||
constraintdropper = SQLiteConstraintDropper
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Module for visitor class mapping.
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from migrate.changeset import ansisql
|
||||
from migrate.changeset.databases import (sqlite,
|
||||
postgres,
|
||||
mysql,
|
||||
oracle,
|
||||
firebird)
|
||||
|
||||
|
||||
# Map SA dialects to the corresponding Migrate extensions
|
||||
DIALECTS = {
|
||||
"default": ansisql.ANSIDialect,
|
||||
"sqlite": sqlite.SQLiteDialect,
|
||||
"postgres": postgres.PGDialect,
|
||||
"postgresql": postgres.PGDialect,
|
||||
"mysql": mysql.MySQLDialect,
|
||||
"oracle": oracle.OracleDialect,
|
||||
"firebird": firebird.FBDialect,
|
||||
}
|
||||
|
||||
|
||||
def get_engine_visitor(engine, name):
|
||||
"""
|
||||
Get the visitor implementation for the given database engine.
|
||||
|
||||
:param engine: SQLAlchemy Engine
|
||||
:param name: Name of the visitor
|
||||
:type name: string
|
||||
:type engine: Engine
|
||||
:returns: visitor
|
||||
"""
|
||||
# TODO: link to supported visitors
|
||||
return get_dialect_visitor(engine.dialect, name)
|
||||
|
||||
|
||||
def get_dialect_visitor(sa_dialect, name):
|
||||
"""
|
||||
Get the visitor implementation for the given dialect.
|
||||
|
||||
Finds the visitor implementation based on the dialect class and
|
||||
returns and instance initialized with the given name.
|
||||
|
||||
Binds dialect specific preparer to visitor.
|
||||
"""
|
||||
|
||||
# map sa dialect to migrate dialect and return visitor
|
||||
sa_dialect_name = getattr(sa_dialect, 'name', 'default')
|
||||
migrate_dialect_cls = DIALECTS[sa_dialect_name]
|
||||
visitor = getattr(migrate_dialect_cls, name)
|
||||
|
||||
# bind preparer
|
||||
visitor.preparer = sa_dialect.preparer(sa_dialect)
|
||||
|
||||
return visitor
|
||||
|
||||
def run_single_visitor(engine, visitorcallable, element,
|
||||
connection=None, **kwargs):
|
||||
"""Taken from :meth:`sqlalchemy.engine.base.Engine._run_single_visitor`
|
||||
with support for migrate visitors.
|
||||
"""
|
||||
if connection is None:
|
||||
conn = engine.contextual_connect(close_with_result=False)
|
||||
else:
|
||||
conn = connection
|
||||
visitor = visitorcallable(engine.dialect, conn)
|
||||
try:
|
||||
if hasattr(element, '__migrate_visit_name__'):
|
||||
fn = getattr(visitor, 'visit_' + element.__migrate_visit_name__)
|
||||
else:
|
||||
fn = getattr(visitor, 'visit_' + element.__visit_name__)
|
||||
fn(element, **kwargs)
|
||||
finally:
|
||||
if connection is None:
|
||||
conn.close()
|
||||
@@ -0,0 +1,651 @@
|
||||
"""
|
||||
Schema module providing common schema operations.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from UserDict import DictMixin
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
from sqlalchemy.schema import ForeignKeyConstraint
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
|
||||
from migrate.exceptions import *
|
||||
from migrate.changeset import SQLA_06
|
||||
from migrate.changeset.databases.visitor import (get_engine_visitor,
|
||||
run_single_visitor)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'create_column',
|
||||
'drop_column',
|
||||
'alter_column',
|
||||
'rename_table',
|
||||
'rename_index',
|
||||
'ChangesetTable',
|
||||
'ChangesetColumn',
|
||||
'ChangesetIndex',
|
||||
'ChangesetDefaultClause',
|
||||
'ColumnDelta',
|
||||
]
|
||||
|
||||
def create_column(column, table=None, *p, **kw):
|
||||
"""Create a column, given the table.
|
||||
|
||||
API to :meth:`ChangesetColumn.create`.
|
||||
"""
|
||||
if table is not None:
|
||||
return table.create_column(column, *p, **kw)
|
||||
return column.create(*p, **kw)
|
||||
|
||||
|
||||
def drop_column(column, table=None, *p, **kw):
|
||||
"""Drop a column, given the table.
|
||||
|
||||
API to :meth:`ChangesetColumn.drop`.
|
||||
"""
|
||||
if table is not None:
|
||||
return table.drop_column(column, *p, **kw)
|
||||
return column.drop(*p, **kw)
|
||||
|
||||
|
||||
def rename_table(table, name, engine=None, **kw):
|
||||
"""Rename a table.
|
||||
|
||||
If Table instance is given, engine is not used.
|
||||
|
||||
API to :meth:`ChangesetTable.rename`.
|
||||
|
||||
:param table: Table to be renamed.
|
||||
:param name: New name for Table.
|
||||
:param engine: Engine instance.
|
||||
:type table: string or Table instance
|
||||
:type name: string
|
||||
:type engine: obj
|
||||
"""
|
||||
table = _to_table(table, engine)
|
||||
table.rename(name, **kw)
|
||||
|
||||
|
||||
def rename_index(index, name, table=None, engine=None, **kw):
|
||||
"""Rename an index.
|
||||
|
||||
If Index instance is given,
|
||||
table and engine are not used.
|
||||
|
||||
API to :meth:`ChangesetIndex.rename`.
|
||||
|
||||
:param index: Index to be renamed.
|
||||
:param name: New name for index.
|
||||
:param table: Table to which Index is reffered.
|
||||
:param engine: Engine instance.
|
||||
:type index: string or Index instance
|
||||
:type name: string
|
||||
:type table: string or Table instance
|
||||
:type engine: obj
|
||||
"""
|
||||
index = _to_index(index, table, engine)
|
||||
index.rename(name, **kw)
|
||||
|
||||
|
||||
def alter_column(*p, **k):
|
||||
"""Alter a column.
|
||||
|
||||
This is a helper function that creates a :class:`ColumnDelta` and
|
||||
runs it.
|
||||
|
||||
:argument column:
|
||||
The name of the column to be altered or a
|
||||
:class:`ChangesetColumn` column representing it.
|
||||
|
||||
:param table:
|
||||
A :class:`~sqlalchemy.schema.Table` or table name to
|
||||
for the table where the column will be changed.
|
||||
|
||||
:param engine:
|
||||
The :class:`~sqlalchemy.engine.base.Engine` to use for table
|
||||
reflection and schema alterations.
|
||||
|
||||
:returns: A :class:`ColumnDelta` instance representing the change.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if 'table' not in k and isinstance(p[0], sqlalchemy.Column):
|
||||
k['table'] = p[0].table
|
||||
if 'engine' not in k:
|
||||
k['engine'] = k['table'].bind
|
||||
|
||||
# deprecation
|
||||
if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
|
||||
warnings.warn(
|
||||
"Passing a Column object to alter_column is deprecated."
|
||||
" Just pass in keyword parameters instead.",
|
||||
MigrateDeprecationWarning
|
||||
)
|
||||
engine = k['engine']
|
||||
|
||||
# enough tests seem to break when metadata is always altered
|
||||
# that this crutch has to be left in until they can be sorted
|
||||
# out
|
||||
k['alter_metadata']=True
|
||||
|
||||
delta = ColumnDelta(*p, **k)
|
||||
|
||||
visitorcallable = get_engine_visitor(engine, 'schemachanger')
|
||||
engine._run_visitor(visitorcallable, delta)
|
||||
|
||||
return delta
|
||||
|
||||
|
||||
def _to_table(table, engine=None):
|
||||
"""Return if instance of Table, else construct new with metadata"""
|
||||
if isinstance(table, sqlalchemy.Table):
|
||||
return table
|
||||
|
||||
# Given: table name, maybe an engine
|
||||
meta = sqlalchemy.MetaData()
|
||||
if engine is not None:
|
||||
meta.bind = engine
|
||||
return sqlalchemy.Table(table, meta)
|
||||
|
||||
|
||||
def _to_index(index, table=None, engine=None):
|
||||
"""Return if instance of Index, else construct new with metadata"""
|
||||
if isinstance(index, sqlalchemy.Index):
|
||||
return index
|
||||
|
||||
# Given: index name; table name required
|
||||
table = _to_table(table, engine)
|
||||
ret = sqlalchemy.Index(index)
|
||||
ret.table = table
|
||||
return ret
|
||||
|
||||
|
||||
class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem):
|
||||
"""Extracts the differences between two columns/column-parameters
|
||||
|
||||
May receive parameters arranged in several different ways:
|
||||
|
||||
* **current_column, new_column, \*p, \*\*kw**
|
||||
Additional parameters can be specified to override column
|
||||
differences.
|
||||
|
||||
* **current_column, \*p, \*\*kw**
|
||||
Additional parameters alter current_column. Table name is extracted
|
||||
from current_column object.
|
||||
Name is changed to current_column.name from current_name,
|
||||
if current_name is specified.
|
||||
|
||||
* **current_col_name, \*p, \*\*kw**
|
||||
Table kw must specified.
|
||||
|
||||
:param table: Table at which current Column should be bound to.\
|
||||
If table name is given, reflection will be used.
|
||||
:type table: string or Table instance
|
||||
|
||||
:param metadata: A :class:`MetaData` instance to store
|
||||
reflected table names
|
||||
|
||||
:param engine: When reflecting tables, either engine or metadata must \
|
||||
be specified to acquire engine object.
|
||||
:type engine: :class:`Engine` instance
|
||||
:returns: :class:`ColumnDelta` instance provides interface for altered attributes to \
|
||||
`result_column` through :func:`dict` alike object.
|
||||
|
||||
* :class:`ColumnDelta`.result_column is altered column with new attributes
|
||||
|
||||
* :class:`ColumnDelta`.current_name is current name of column in db
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Column attributes that can be altered
|
||||
diff_keys = ('name', 'type', 'primary_key', 'nullable',
|
||||
'server_onupdate', 'server_default', 'autoincrement')
|
||||
diffs = dict()
|
||||
__visit_name__ = 'column'
|
||||
|
||||
def __init__(self, *p, **kw):
|
||||
# 'alter_metadata' is not a public api. It exists purely
|
||||
# as a crutch until the tests that fail when 'alter_metadata'
|
||||
# behaviour always happens can be sorted out
|
||||
self.alter_metadata = kw.pop("alter_metadata", False)
|
||||
|
||||
self.meta = kw.pop("metadata", None)
|
||||
self.engine = kw.pop("engine", None)
|
||||
|
||||
# Things are initialized differently depending on how many column
|
||||
# parameters are given. Figure out how many and call the appropriate
|
||||
# method.
|
||||
if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column):
|
||||
# At least one column specified
|
||||
if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
|
||||
# Two columns specified
|
||||
diffs = self.compare_2_columns(*p, **kw)
|
||||
else:
|
||||
# Exactly one column specified
|
||||
diffs = self.compare_1_column(*p, **kw)
|
||||
else:
|
||||
# Zero columns specified
|
||||
if not len(p) or not isinstance(p[0], basestring):
|
||||
raise ValueError("First argument must be column name")
|
||||
diffs = self.compare_parameters(*p, **kw)
|
||||
|
||||
self.apply_diffs(diffs)
|
||||
|
||||
def __repr__(self):
|
||||
return '<ColumnDelta altermetadata=%r, %s>' % (
|
||||
self.alter_metadata,
|
||||
super(ColumnDelta, self).__repr__()
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self.keys():
|
||||
raise KeyError("No such diff key, available: %s" % self.diffs )
|
||||
return getattr(self.result_column, key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self.keys():
|
||||
raise KeyError("No such diff key, available: %s" % self.diffs )
|
||||
setattr(self.result_column, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def keys(self):
|
||||
return self.diffs.keys()
|
||||
|
||||
def compare_parameters(self, current_name, *p, **k):
|
||||
"""Compares Column objects with reflection"""
|
||||
self.table = k.pop('table')
|
||||
self.result_column = self._table.c.get(current_name)
|
||||
if len(p):
|
||||
k = self._extract_parameters(p, k, self.result_column)
|
||||
return k
|
||||
|
||||
def compare_1_column(self, col, *p, **k):
|
||||
"""Compares one Column object"""
|
||||
self.table = k.pop('table', None)
|
||||
if self.table is None:
|
||||
self.table = col.table
|
||||
self.result_column = col
|
||||
if len(p):
|
||||
k = self._extract_parameters(p, k, self.result_column)
|
||||
return k
|
||||
|
||||
def compare_2_columns(self, old_col, new_col, *p, **k):
|
||||
"""Compares two Column objects"""
|
||||
self.process_column(new_col)
|
||||
self.table = k.pop('table', None)
|
||||
# we cannot use bool() on table in SA06
|
||||
if self.table is None:
|
||||
self.table = old_col.table
|
||||
if self.table is None:
|
||||
new_col.table
|
||||
self.result_column = old_col
|
||||
|
||||
# set differences
|
||||
# leave out some stuff for later comp
|
||||
for key in (set(self.diff_keys) - set(('type',))):
|
||||
val = getattr(new_col, key, None)
|
||||
if getattr(self.result_column, key, None) != val:
|
||||
k.setdefault(key, val)
|
||||
|
||||
# inspect types
|
||||
if not self.are_column_types_eq(self.result_column.type, new_col.type):
|
||||
k.setdefault('type', new_col.type)
|
||||
|
||||
if len(p):
|
||||
k = self._extract_parameters(p, k, self.result_column)
|
||||
return k
|
||||
|
||||
def apply_diffs(self, diffs):
|
||||
"""Populate dict and column object with new values"""
|
||||
self.diffs = diffs
|
||||
for key in self.diff_keys:
|
||||
if key in diffs:
|
||||
setattr(self.result_column, key, diffs[key])
|
||||
|
||||
self.process_column(self.result_column)
|
||||
|
||||
# create an instance of class type if not yet
|
||||
if 'type' in diffs and callable(self.result_column.type):
|
||||
self.result_column.type = self.result_column.type()
|
||||
|
||||
# add column to the table
|
||||
if self.table is not None and self.alter_metadata:
|
||||
self.result_column.add_to_table(self.table)
|
||||
|
||||
def are_column_types_eq(self, old_type, new_type):
|
||||
"""Compares two types to be equal"""
|
||||
ret = old_type.__class__ == new_type.__class__
|
||||
|
||||
# String length is a special case
|
||||
if ret and isinstance(new_type, sqlalchemy.types.String):
|
||||
ret = (getattr(old_type, 'length', None) == \
|
||||
getattr(new_type, 'length', None))
|
||||
return ret
|
||||
|
||||
def _extract_parameters(self, p, k, column):
|
||||
"""Extracts data from p and modifies diffs"""
|
||||
p = list(p)
|
||||
while len(p):
|
||||
if isinstance(p[0], basestring):
|
||||
k.setdefault('name', p.pop(0))
|
||||
elif isinstance(p[0], sqlalchemy.types.AbstractType):
|
||||
k.setdefault('type', p.pop(0))
|
||||
elif callable(p[0]):
|
||||
p[0] = p[0]()
|
||||
else:
|
||||
break
|
||||
|
||||
if len(p):
|
||||
new_col = column.copy_fixed()
|
||||
new_col._init_items(*p)
|
||||
k = self.compare_2_columns(column, new_col, **k)
|
||||
return k
|
||||
|
||||
def process_column(self, column):
|
||||
"""Processes default values for column"""
|
||||
# XXX: this is a snippet from SA processing of positional parameters
|
||||
if not SQLA_06 and column.args:
|
||||
toinit = list(column.args)
|
||||
else:
|
||||
toinit = list()
|
||||
|
||||
if column.server_default is not None:
|
||||
if isinstance(column.server_default, sqlalchemy.FetchedValue):
|
||||
toinit.append(column.server_default)
|
||||
else:
|
||||
toinit.append(sqlalchemy.DefaultClause(column.server_default))
|
||||
if column.server_onupdate is not None:
|
||||
if isinstance(column.server_onupdate, FetchedValue):
|
||||
toinit.append(column.server_default)
|
||||
else:
|
||||
toinit.append(sqlalchemy.DefaultClause(column.server_onupdate,
|
||||
for_update=True))
|
||||
if toinit:
|
||||
column._init_items(*toinit)
|
||||
|
||||
if not SQLA_06:
|
||||
column.args = []
|
||||
|
||||
def _get_table(self):
|
||||
return getattr(self, '_table', None)
|
||||
|
||||
def _set_table(self, table):
|
||||
if isinstance(table, basestring):
|
||||
if self.alter_metadata:
|
||||
if not self.meta:
|
||||
raise ValueError("metadata must be specified for table"
|
||||
" reflection when using alter_metadata")
|
||||
meta = self.meta
|
||||
if self.engine:
|
||||
meta.bind = self.engine
|
||||
else:
|
||||
if not self.engine and not self.meta:
|
||||
raise ValueError("engine or metadata must be specified"
|
||||
" to reflect tables")
|
||||
if not self.engine:
|
||||
self.engine = self.meta.bind
|
||||
meta = sqlalchemy.MetaData(bind=self.engine)
|
||||
self._table = sqlalchemy.Table(table, meta, autoload=True)
|
||||
elif isinstance(table, sqlalchemy.Table):
|
||||
self._table = table
|
||||
if not self.alter_metadata:
|
||||
self._table.meta = sqlalchemy.MetaData(bind=self._table.bind)
|
||||
def _get_result_column(self):
|
||||
return getattr(self, '_result_column', None)
|
||||
|
||||
def _set_result_column(self, column):
|
||||
"""Set Column to Table based on alter_metadata evaluation."""
|
||||
self.process_column(column)
|
||||
if not hasattr(self, 'current_name'):
|
||||
self.current_name = column.name
|
||||
if self.alter_metadata:
|
||||
self._result_column = column
|
||||
else:
|
||||
self._result_column = column.copy_fixed()
|
||||
|
||||
table = property(_get_table, _set_table)
|
||||
result_column = property(_get_result_column, _set_result_column)
|
||||
|
||||
|
||||
class ChangesetTable(object):
|
||||
"""Changeset extensions to SQLAlchemy tables."""
|
||||
|
||||
def create_column(self, column, *p, **kw):
|
||||
"""Creates a column.
|
||||
|
||||
The column parameter may be a column definition or the name of
|
||||
a column in this table.
|
||||
|
||||
API to :meth:`ChangesetColumn.create`
|
||||
|
||||
:param column: Column to be created
|
||||
:type column: Column instance or string
|
||||
"""
|
||||
if not isinstance(column, sqlalchemy.Column):
|
||||
# It's a column name
|
||||
column = getattr(self.c, str(column))
|
||||
column.create(table=self, *p, **kw)
|
||||
|
||||
def drop_column(self, column, *p, **kw):
|
||||
"""Drop a column, given its name or definition.
|
||||
|
||||
API to :meth:`ChangesetColumn.drop`
|
||||
|
||||
:param column: Column to be droped
|
||||
:type column: Column instance or string
|
||||
"""
|
||||
if not isinstance(column, sqlalchemy.Column):
|
||||
# It's a column name
|
||||
try:
|
||||
column = getattr(self.c, str(column))
|
||||
except AttributeError:
|
||||
# That column isn't part of the table. We don't need
|
||||
# its entire definition to drop the column, just its
|
||||
# name, so create a dummy column with the same name.
|
||||
column = sqlalchemy.Column(str(column), sqlalchemy.Integer())
|
||||
column.drop(table=self, *p, **kw)
|
||||
|
||||
def rename(self, name, connection=None, **kwargs):
|
||||
"""Rename this table.
|
||||
|
||||
:param name: New name of the table.
|
||||
:type name: string
|
||||
:param connection: reuse connection istead of creating new one.
|
||||
:type connection: :class:`sqlalchemy.engine.base.Connection` instance
|
||||
"""
|
||||
engine = self.bind
|
||||
self.new_name = name
|
||||
visitorcallable = get_engine_visitor(engine, 'schemachanger')
|
||||
run_single_visitor(engine, visitorcallable, self, connection, **kwargs)
|
||||
|
||||
# Fix metadata registration
|
||||
self.name = name
|
||||
self.deregister()
|
||||
self._set_parent(self.metadata)
|
||||
|
||||
def _meta_key(self):
|
||||
return sqlalchemy.schema._get_table_key(self.name, self.schema)
|
||||
|
||||
def deregister(self):
|
||||
"""Remove this table from its metadata"""
|
||||
key = self._meta_key()
|
||||
meta = self.metadata
|
||||
if key in meta.tables:
|
||||
del meta.tables[key]
|
||||
|
||||
|
||||
class ChangesetColumn(object):
|
||||
"""Changeset extensions to SQLAlchemy columns."""
|
||||
|
||||
def alter(self, *p, **k):
|
||||
"""Makes a call to :func:`alter_column` for the column this
|
||||
method is called on.
|
||||
"""
|
||||
if 'table' not in k:
|
||||
k['table'] = self.table
|
||||
if 'engine' not in k:
|
||||
k['engine'] = k['table'].bind
|
||||
return alter_column(self, *p, **k)
|
||||
|
||||
def create(self, table=None, index_name=None, unique_name=None,
|
||||
primary_key_name=None, populate_default=True, connection=None, **kwargs):
|
||||
"""Create this column in the database.
|
||||
|
||||
Assumes the given table exists. ``ALTER TABLE ADD COLUMN``,
|
||||
for most databases.
|
||||
|
||||
:param table: Table instance to create on.
|
||||
:param index_name: Creates :class:`ChangesetIndex` on this column.
|
||||
:param unique_name: Creates :class:\
|
||||
`~migrate.changeset.constraint.UniqueConstraint` on this column.
|
||||
:param primary_key_name: Creates :class:\
|
||||
`~migrate.changeset.constraint.PrimaryKeyConstraint` on this column.
|
||||
:param populate_default: If True, created column will be \
|
||||
populated with defaults
|
||||
:param connection: reuse connection istead of creating new one.
|
||||
:type table: Table instance
|
||||
:type index_name: string
|
||||
:type unique_name: string
|
||||
:type primary_key_name: string
|
||||
:type populate_default: bool
|
||||
:type connection: :class:`sqlalchemy.engine.base.Connection` instance
|
||||
|
||||
:returns: self
|
||||
"""
|
||||
self.populate_default = populate_default
|
||||
self.index_name = index_name
|
||||
self.unique_name = unique_name
|
||||
self.primary_key_name = primary_key_name
|
||||
for cons in ('index_name', 'unique_name', 'primary_key_name'):
|
||||
self._check_sanity_constraints(cons)
|
||||
|
||||
self.add_to_table(table)
|
||||
engine = self.table.bind
|
||||
visitorcallable = get_engine_visitor(engine, 'columngenerator')
|
||||
engine._run_visitor(visitorcallable, self, connection, **kwargs)
|
||||
|
||||
# TODO: reuse existing connection
|
||||
if self.populate_default and self.default is not None:
|
||||
stmt = table.update().values({self: engine._execute_default(self.default)})
|
||||
engine.execute(stmt)
|
||||
|
||||
return self
|
||||
|
||||
def drop(self, table=None, connection=None, **kwargs):
|
||||
"""Drop this column from the database, leaving its table intact.
|
||||
|
||||
``ALTER TABLE DROP COLUMN``, for most databases.
|
||||
|
||||
:param connection: reuse connection istead of creating new one.
|
||||
:type connection: :class:`sqlalchemy.engine.base.Connection` instance
|
||||
"""
|
||||
if table is not None:
|
||||
self.table = table
|
||||
engine = self.table.bind
|
||||
visitorcallable = get_engine_visitor(engine, 'columndropper')
|
||||
engine._run_visitor(visitorcallable, self, connection, **kwargs)
|
||||
self.remove_from_table(self.table, unset_table=False)
|
||||
self.table = None
|
||||
return self
|
||||
|
||||
def add_to_table(self, table):
|
||||
if table is not None and self.table is None:
|
||||
self._set_parent(table)
|
||||
|
||||
def _col_name_in_constraint(self,cons,name):
|
||||
return False
|
||||
|
||||
def remove_from_table(self, table, unset_table=True):
|
||||
# TODO: remove primary keys, constraints, etc
|
||||
if unset_table:
|
||||
self.table = None
|
||||
|
||||
to_drop = set()
|
||||
for index in table.indexes:
|
||||
columns = []
|
||||
for col in index.columns:
|
||||
if col.name!=self.name:
|
||||
columns.append(col)
|
||||
if columns:
|
||||
index.columns=columns
|
||||
else:
|
||||
to_drop.add(index)
|
||||
table.indexes = table.indexes - to_drop
|
||||
|
||||
to_drop = set()
|
||||
for cons in table.constraints:
|
||||
# TODO: deal with other types of constraint
|
||||
if isinstance(cons,(ForeignKeyConstraint,
|
||||
UniqueConstraint)):
|
||||
for col_name in cons.columns:
|
||||
if not isinstance(col_name,basestring):
|
||||
col_name = col_name.name
|
||||
if self.name==col_name:
|
||||
to_drop.add(cons)
|
||||
table.constraints = table.constraints - to_drop
|
||||
|
||||
if table.c.contains_column(self):
|
||||
table.c.remove(self)
|
||||
|
||||
# TODO: this is fixed in 0.6
|
||||
def copy_fixed(self, **kw):
|
||||
"""Create a copy of this ``Column``, with all attributes."""
|
||||
return sqlalchemy.Column(self.name, self.type, self.default,
|
||||
key=self.key,
|
||||
primary_key=self.primary_key,
|
||||
nullable=self.nullable,
|
||||
quote=self.quote,
|
||||
index=self.index,
|
||||
unique=self.unique,
|
||||
onupdate=self.onupdate,
|
||||
autoincrement=self.autoincrement,
|
||||
server_default=self.server_default,
|
||||
server_onupdate=self.server_onupdate,
|
||||
*[c.copy(**kw) for c in self.constraints])
|
||||
|
||||
def _check_sanity_constraints(self, name):
|
||||
"""Check if constraints names are correct"""
|
||||
obj = getattr(self, name)
|
||||
if (getattr(self, name[:-5]) and not obj):
|
||||
raise InvalidConstraintError("Column.create() accepts index_name,"
|
||||
" primary_key_name and unique_name to generate constraints")
|
||||
if not isinstance(obj, basestring) and obj is not None:
|
||||
raise InvalidConstraintError(
|
||||
"%s argument for column must be constraint name" % name)
|
||||
|
||||
|
||||
class ChangesetIndex(object):
|
||||
"""Changeset extensions to SQLAlchemy Indexes."""
|
||||
|
||||
__visit_name__ = 'index'
|
||||
|
||||
def rename(self, name, connection=None, **kwargs):
|
||||
"""Change the name of an index.
|
||||
|
||||
:param name: New name of the Index.
|
||||
:type name: string
|
||||
:param connection: reuse connection istead of creating new one.
|
||||
:type connection: :class:`sqlalchemy.engine.base.Connection` instance
|
||||
"""
|
||||
engine = self.table.bind
|
||||
self.new_name = name
|
||||
visitorcallable = get_engine_visitor(engine, 'schemachanger')
|
||||
engine._run_visitor(visitorcallable, self, connection, **kwargs)
|
||||
self.name = name
|
||||
|
||||
|
||||
class ChangesetDefaultClause(object):
|
||||
"""Implements comparison between :class:`DefaultClause` instances"""
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
if self.arg == other.arg:
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
Reference in New Issue
Block a user