9195a05684
Also add support for dropping sqlite columns in migrations. Replace any references to user names with references to account table entries. Change-Id: I5a147370f5ee648ddd808f699e80a7778c21d662
173 lines
6.0 KiB
Python
173 lines
6.0 KiB
Python
# Copyright 2014 Mirantis Inc.
|
|
# Copyright 2014 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import six
|
|
import uuid
|
|
|
|
from alembic import op
|
|
import sqlalchemy
|
|
|
|
|
|
def sqlite_alter_columns(table_name, column_defs):
|
|
"""Implement alter columns for SQLite.
|
|
|
|
The ALTER COLUMN command isn't supported by SQLite specification.
|
|
Instead of calling ALTER COLUMN it uses the following workaround:
|
|
|
|
* create temp table '{table_name}_{rand_uuid}', with some column
|
|
defs replaced;
|
|
* copy all data to the temp table;
|
|
* drop old table;
|
|
* rename temp table to the old table name.
|
|
"""
|
|
connection = op.get_bind()
|
|
meta = sqlalchemy.MetaData(bind=connection)
|
|
meta.reflect()
|
|
|
|
changed_columns = {}
|
|
indexes = []
|
|
for col in column_defs:
|
|
# If we are to have an index on the column, don't create it
|
|
# immediately, instead, add it to a list of indexes to create
|
|
# after the table rename.
|
|
if col.index:
|
|
indexes.append(('ix_%s_%s' % (table_name, col.name),
|
|
table_name,
|
|
[col.name],
|
|
col.unique))
|
|
col.unique = False
|
|
col.index = False
|
|
changed_columns[col.name] = col
|
|
|
|
# construct lists of all columns and their names
|
|
old_columns = []
|
|
new_columns = []
|
|
column_names = []
|
|
for column in meta.tables[table_name].columns:
|
|
column_names.append(column.name)
|
|
old_columns.append(column)
|
|
if column.name in changed_columns.keys():
|
|
new_columns.append(changed_columns[column.name])
|
|
else:
|
|
col_copy = column.copy()
|
|
new_columns.append(col_copy)
|
|
|
|
for key in meta.tables[table_name].foreign_keys:
|
|
constraint = key.constraint
|
|
con_copy = constraint.copy()
|
|
new_columns.append(con_copy)
|
|
|
|
for index in meta.tables[table_name].indexes:
|
|
# If this is a single column index for a changed column, don't
|
|
# copy it because we may already be creating a new version of
|
|
# it (or removing it).
|
|
idx_columns = [col.name for col in index.columns]
|
|
if len(idx_columns)==1 and idx_columns[0] in changed_columns.keys():
|
|
continue
|
|
# Otherwise, recreate the index.
|
|
indexes.append((index.name,
|
|
table_name,
|
|
[col.name for col in index.columns],
|
|
index.unique))
|
|
|
|
# create temp table
|
|
tmp_table_name = "%s_%s" % (table_name, six.text_type(uuid.uuid4()))
|
|
op.create_table(tmp_table_name, *new_columns)
|
|
meta.reflect()
|
|
|
|
try:
|
|
# copy data from the old table to the temp one
|
|
sql_select = sqlalchemy.sql.select(old_columns)
|
|
connection.execute(sqlalchemy.sql.insert(meta.tables[tmp_table_name])
|
|
.from_select(column_names, sql_select))
|
|
except Exception:
|
|
op.drop_table(tmp_table_name)
|
|
raise
|
|
|
|
# drop the old table and rename temp table to the old table name
|
|
op.drop_table(table_name)
|
|
op.rename_table(tmp_table_name, table_name)
|
|
|
|
# (re-)create indexes
|
|
for index in indexes:
|
|
op.create_index(op.f(index[0]), index[1], index[2], unique=index[3])
|
|
|
|
def sqlite_drop_columns(table_name, drop_columns):
|
|
"""Implement drop columns for SQLite.
|
|
|
|
The DROP COLUMN command isn't supported by SQLite specification.
|
|
Instead of calling DROP COLUMN it uses the following workaround:
|
|
|
|
* create temp table '{table_name}_{rand_uuid}', without
|
|
dropped columns;
|
|
* copy all data to the temp table;
|
|
* drop old table;
|
|
* rename temp table to the old table name.
|
|
"""
|
|
connection = op.get_bind()
|
|
meta = sqlalchemy.MetaData(bind=connection)
|
|
meta.reflect()
|
|
|
|
# construct lists of all columns and their names
|
|
old_columns = []
|
|
new_columns = []
|
|
column_names = []
|
|
indexes = []
|
|
for column in meta.tables[table_name].columns:
|
|
if column.name not in drop_columns:
|
|
old_columns.append(column)
|
|
column_names.append(column.name)
|
|
col_copy = column.copy()
|
|
new_columns.append(col_copy)
|
|
|
|
for key in meta.tables[table_name].foreign_keys:
|
|
constraint = key.constraint
|
|
con_copy = constraint.copy()
|
|
new_columns.append(con_copy)
|
|
|
|
for index in meta.tables[table_name].indexes:
|
|
# If this is a single column index for a dropped column, don't
|
|
# copy it.
|
|
idx_columns = [col.name for col in index.columns]
|
|
if len(idx_columns)==1 and idx_columns[0] in drop_columns:
|
|
continue
|
|
# Otherwise, recreate the index.
|
|
indexes.append((index.name,
|
|
table_name,
|
|
[col.name for col in index.columns],
|
|
index.unique))
|
|
|
|
# create temp table
|
|
tmp_table_name = "%s_%s" % (table_name, six.text_type(uuid.uuid4()))
|
|
op.create_table(tmp_table_name, *new_columns)
|
|
meta.reflect()
|
|
|
|
try:
|
|
# copy data from the old table to the temp one
|
|
sql_select = sqlalchemy.sql.select(old_columns)
|
|
connection.execute(sqlalchemy.sql.insert(meta.tables[tmp_table_name])
|
|
.from_select(column_names, sql_select))
|
|
except Exception:
|
|
op.drop_table(tmp_table_name)
|
|
raise
|
|
|
|
# drop the old table and rename temp table to the old table name
|
|
op.drop_table(table_name)
|
|
op.rename_table(tmp_table_name, table_name)
|
|
|
|
# (re-)create indexes
|
|
for index in indexes:
|
|
op.create_index(op.f(index[0]), index[1], index[2], unique=index[3])
|