From 2c590b82b716ecfca9b683afa1181a8368b6cb41 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Wed, 7 May 2014 21:33:30 -0400 Subject: [PATCH] Add some helper methods to deal with sqlite migrations SQLite has very little support for altering tables. Add some helper methods mostly written by Sergey Lukjanov to work around these issues. Change-Id: I4a68fc0d6291e72c88c511d5f64befa41363df2c Co-Authored-By: Sergey Lukjanov --- gertty/dbsupport.py | 105 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 gertty/dbsupport.py diff --git a/gertty/dbsupport.py b/gertty/dbsupport.py new file mode 100644 index 0000000..0365b63 --- /dev/null +++ b/gertty/dbsupport.py @@ -0,0 +1,105 @@ +# 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])