Move patch from oslo to drop unique constraints with sqlite
oslo-incubator commit 3f503faac for making sqlite work with dropping unique constraints in database migrations. This was made in oslo-incubator since at the time sqlalchemy-migrate was not in stackforge. Now that we can update sqlalchemy-migrate, move the patch over from oslo. This change also adds the support for the case that a unique constraint is dropped because the column it's on is dropped. Note that there are already unit tests that cover dropping a unique constraint directly and implicitly via dropping a column that is in the unique constraint. Related-Bug: #1307266 Change-Id: I5ee8082a83aebf66f6e1dacb093ed79e13f73f5e
This commit is contained in:
parent
07909159ae
commit
93efb62fd1
@ -5,8 +5,10 @@
|
|||||||
"""
|
"""
|
||||||
from UserDict import DictMixin
|
from UserDict import DictMixin
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
import re
|
||||||
|
|
||||||
from sqlalchemy.databases import sqlite as sa_base
|
from sqlalchemy.databases import sqlite as sa_base
|
||||||
|
from sqlalchemy.schema import UniqueConstraint
|
||||||
|
|
||||||
from migrate import exceptions
|
from migrate import exceptions
|
||||||
from migrate.changeset import ansisql
|
from migrate.changeset import ansisql
|
||||||
@ -24,7 +26,38 @@ class SQLiteCommon(object):
|
|||||||
|
|
||||||
class SQLiteHelper(SQLiteCommon):
|
class SQLiteHelper(SQLiteCommon):
|
||||||
|
|
||||||
def recreate_table(self,table,column=None,delta=None):
|
def _get_unique_constraints(self, table):
|
||||||
|
"""Retrieve information about existing unique constraints of the table
|
||||||
|
|
||||||
|
This feature is needed for recreate_table() to work properly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = table.metadata.bind.execute(
|
||||||
|
"""SELECT sql
|
||||||
|
FROM sqlite_master
|
||||||
|
WHERE
|
||||||
|
type='table' AND
|
||||||
|
name=:table_name""",
|
||||||
|
table_name=table.name
|
||||||
|
).fetchone()[0]
|
||||||
|
|
||||||
|
UNIQUE_PATTERN = "CONSTRAINT (\w+) UNIQUE \(([^\)]+)\)"
|
||||||
|
constraints = []
|
||||||
|
for name, cols in re.findall(UNIQUE_PATTERN, data):
|
||||||
|
# Filter out any columns that were dropped from the table.
|
||||||
|
columns = []
|
||||||
|
for c in cols.split(","):
|
||||||
|
if c in table.columns:
|
||||||
|
# There was a bug in reflection of SQLite columns with
|
||||||
|
# reserved identifiers as names (SQLite can return them
|
||||||
|
# wrapped with double quotes), so strip double quotes.
|
||||||
|
columns.extend(c.strip(' "'))
|
||||||
|
if columns:
|
||||||
|
constraints.extend(UniqueConstraint(*columns, name=name))
|
||||||
|
return constraints
|
||||||
|
|
||||||
|
def recreate_table(self, table, column=None, delta=None,
|
||||||
|
omit_uniques=None):
|
||||||
table_name = self.preparer.format_table(table)
|
table_name = self.preparer.format_table(table)
|
||||||
|
|
||||||
# we remove all indexes so as not to have
|
# we remove all indexes so as not to have
|
||||||
@ -32,6 +65,15 @@ class SQLiteHelper(SQLiteCommon):
|
|||||||
for index in table.indexes:
|
for index in table.indexes:
|
||||||
index.drop()
|
index.drop()
|
||||||
|
|
||||||
|
# reflect existing unique constraints
|
||||||
|
for uc in self._get_unique_constraints(table):
|
||||||
|
table.append_constraint(uc)
|
||||||
|
# omit given unique constraints when creating a new table if required
|
||||||
|
table.constraints = set([
|
||||||
|
cons for cons in table.constraints
|
||||||
|
if omit_uniques is None or cons.name not in omit_uniques
|
||||||
|
])
|
||||||
|
|
||||||
self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
|
self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
|
||||||
self.execute()
|
self.execute()
|
||||||
|
|
||||||
@ -123,9 +165,12 @@ class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, S
|
|||||||
|
|
||||||
|
|
||||||
class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
|
class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
|
||||||
SQLiteCommon,
|
SQLiteHelper,
|
||||||
ansisql.ANSIConstraintCommon):
|
ansisql.ANSIConstraintCommon):
|
||||||
|
|
||||||
|
def _modify_table(self, table, column, delta):
|
||||||
|
return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
|
||||||
|
|
||||||
def visit_migrate_primary_key_constraint(self, constraint):
|
def visit_migrate_primary_key_constraint(self, constraint):
|
||||||
tmpl = "DROP INDEX %s "
|
tmpl = "DROP INDEX %s "
|
||||||
name = self.get_constraint_name(constraint)
|
name = self.get_constraint_name(constraint)
|
||||||
@ -140,7 +185,7 @@ class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
|
|||||||
self._not_supported('ALTER TABLE DROP CONSTRAINT')
|
self._not_supported('ALTER TABLE DROP CONSTRAINT')
|
||||||
|
|
||||||
def visit_migrate_unique_constraint(self, *p, **k):
|
def visit_migrate_unique_constraint(self, *p, **k):
|
||||||
self._not_supported('ALTER TABLE DROP CONSTRAINT')
|
self.recreate_table(p[0].table, omit_uniques=[p[0].name])
|
||||||
|
|
||||||
|
|
||||||
# TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
|
# TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
|
||||||
|
@ -274,7 +274,7 @@ class TestAutoname(CommonTestConstraint):
|
|||||||
self.table.insert(values={'id': 2, 'fkey': 2}).execute()
|
self.table.insert(values={'id': 2, 'fkey': 2}).execute()
|
||||||
self.table.insert(values={'id': 1, 'fkey': 3}).execute()
|
self.table.insert(values={'id': 1, 'fkey': 3}).execute()
|
||||||
|
|
||||||
@fixture.usedb(not_supported=['oracle', 'sqlite'])
|
@fixture.usedb(not_supported=['oracle'])
|
||||||
def test_autoname_unique(self):
|
def test_autoname_unique(self):
|
||||||
"""UniqueConstraints can guess their name if None is given"""
|
"""UniqueConstraints can guess their name if None is given"""
|
||||||
cons = UniqueConstraint(self.table.c.fkey)
|
cons = UniqueConstraint(self.table.c.fkey)
|
||||||
|
Loading…
Reference in New Issue
Block a user