ORM позволяет быстро переключатся между БД не учитывая их диалект(практически). Но данные хранятся физически в разных местах и естественно их надо переносить, например при переключении с sqlite на PostgreSQL. В Django есть встроенный функционал в виде:# Выгрузка в JSON python manage.py dumpdata myapp.A > a.json # Загрузка из JSON python manage.py loaddata a.json
Т.е. мы выгружаем данные из sqlite в JSON формат, затем меняем строку подключения на postgres и выполняем загрузку из JSON. Очень удобно, но почему то этот метод не работает, либо работает только при переносе из sqlite -> sqlite, что в принципе не очень интересно, точнее бессмысленно. Есть какие то решений с бубном как это вот http://macrotoma.blogspot.ru/2012/10/solved-move-django-from-sqlite-to-mysql.html, http://blog.abourget.net/2009/7/7/exporting-sql-schemas-from-sqlalchemy-table-definitions/.
Эти методы не универсальны, потому что имеют привязку к моделям(ORM), требуют для переноса проект на Django и ручные действия вроде создания схемы и выполнения миграций(далеко не всегда миграции созданы правильно).
Я написал небольшой пример как можно перевести данные не имея фреймворков, не привязываясь к моделям, указав только две строки подключения откуда переносить и куда(вроде было что то похожее на руби). За основы взят пример из этой статьи http://www.tylerlesmann.com/2009/apr/27/copying-databases-across-platforms-sqlalchemy/. Где предлагается указать дополнительно названия таблиц.
В sqlalchemy с весии 9.1 появилась встроенная возможность автоматического определения схемы БД http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/automap.html. Правда намного раньше появились сторонние решения: http://turbogears.org/2.1/docs/main/Utilities/sqlautocode.html или https://sqlsoup.readthedocs.org/en/latest/.
В sqlalchemy с весии 9.1 появилась встроенная возможность автоматического определения схемы БД http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/automap.html. Правда намного раньше появились сторонние решения: http://turbogears.org/2.1/docs/main/Utilities/sqlautocode.html или https://sqlsoup.readthedocs.org/en/latest/.
Первое что мы сделаем, это получим схему БД
from sqlalchemy.ext.automap import automap_base
def get_metadata(self, engine):
# produce our own MetaData object
metadata = MetaData()
# we can reflect it ourselves from a database, using options
# such as 'only' to limit what tables we look at...
# only = ['news_news', 'pages_page']
if self.only:
metadata.reflect(engine, only=self.only)
else:
metadata.reflect(engine)
# we can then produce a set of mappings from this MetaData.
Base = automap_base(metadata=metadata)
# calling prepare() just sets up mapped classes and relationships.
Base.prepare()
return metadata, Base
Дальше получаем все таблицы
tables = Base.classes
Создаем такую же структуру БД в postgres:
metadata.create_all(self.engine_dst)
По очереди проходим каждую таблицу и переносим из нее данные в новую БД
for table in tables:
columns = table.__table__.c.keys()
print 'Transferring records to %s' % table.__table__.name
for record in self.session.query(table).all():
data = dict(
[(str(column), getattr(record, column)) for column in columns]
)
NewRecord = quick_mapper(table.__table__)
self.session_dst.merge(NewRecord(**data))
self.session_dst.commit()
Если возникли конфликты можно их решить переопределением типов полей. Например при переносе из sqlite в postgres тип полей DATETIME нужно заменить на DateTime
dialect = self.engine.dialect.name
dialect_dst = self.engine_dst.dialect.name
if dialect == dialect_dst:
return
for table in self.tables:
columns = table.__table__.c
for column in columns:
if dialect_dst == 'postgresql':
# DATETIME->DateTime
if isinstance(column.type, DATETIME):
column.type = DateTime()
Пример запуска:
-i параметр указывает какие таблицы нужно запускать первыми, например в такой ситуации:
Код полностью https://github.com/ITCase/convertdbdata
Сейчас скрипт не рассчитан на большие объемы данных и учитывает только разницу в DateTime поле, но думаю это легко исправить по мере поступления задач.
python convertdbdata.py -f "sqlite:///fromMydb.sqlite" -t "postgresql://postgres:postgres@localhost/toMydb" -i "auth_user,news_news"
-i параметр указывает какие таблицы нужно запускать первыми, например в такой ситуации:
DETAIL: Ключ (user_id)=(1) отсутствует в таблице "auth_user".
'INSERT INTO django_admin_log (id, action_time, user_id, content_type_id, object_id, object_repr, action_flag,
change_message) VALUES (%(id)s, %(action_time)s, %(user_id)s, %(content_type_id)s, %(object_id)s, %(object_repr)s,
%(action_flag)s, %(change_message)s)' {'action_flag': 1, 'action_time': datetime.datetime(2014, 2, 5, 13, 15, 27, 948000),
'user_id': 1, 'content_type_id': 39, 'object_repr': u'dfgsdfg', 'object_id': u'1', 'change_message': u'', 'id': 1}
Код полностью https://github.com/ITCase/convertdbdata
Сейчас скрипт не рассчитан на большие объемы данных и учитывает только разницу в DateTime поле, но думаю это легко исправить по мере поступления задач.
А я бы так для интереса попробовал:
ОтветитьУдалить1. Инициализируем при помощи manage структуру новой БД;
2. Делаем DML sql-дамп с исходной БД;
3. Заливаем дамп в новую БД.
В моём случае возникла проблема с типом smallint unsigned(Postgres не знает про unsigned) и Boolean в sqlite значения в виде цифр типа 1,0 а в Postgres в виде строк '1','0'. А так работает :)
УдалитьДэк, в DDL не будет никаких упоминаний про unsigned, а касательно boolean существует решение со временным изменением инструкции castcontext в таблице pg_cast на время заливки дампа.
Удалить