Как стать автором
Поиск
Написать публикацию
Обновить

DDB поверх RDB на Питоне

Время на прочтение3 мин
Количество просмотров617
Несколько месяцев назад мне понадобилась документная база данных для одного планируемого проекта на Python. Не знаю как это получилось, но при гуглении я умудрился не заметить ни одного существующего решения и пришел к выводу, что единственный выход — написать свою. Так как мне хотелось получить работающий вариант как можно быстрее, я решил отталкиваться от существующих реляционных баз данных, чтобы не мучаться над реализацией хранилища, поиска, транзакций и тому подобных вещей. В конце концов мое творение приобрело приличный вид, и я решил написать заметку о нем сюда — может, кому еще и пригодится.

Где взять



Для самых нетерпеливых:


База данных выполнена в виде модуля и требует для использования Python 3 (а для запуска юниттестов — Python 3.1, уж очень там хорошие методы добавились). Сразу прошу прощения у всех ортодоксов, которые до сих пор не могут решиться перейти даже на 2.6 — я, конечно, мог бы портировать свой модуль на 2.*, но мне было лень.

Как я уже говорил, модуль использует для работы обычную реляционную БД в качестве нижнего слоя. На данный момент поддерживается SQLite (тот, что идет вместе с Python) и Postgre 8 (при наличии установленного py-postgresql).

… и что с этим делать



Я полагаю, что документные базы данных многим уже знакомы, так что буду краток. Итак, основным понятием у нас является объект. У объекта есть уникальный идентификатор и некоторое количество данных различных типов. В моей БД данные являются комбинацией простейших типов — int, float, str, bytes и None — и сложных типов — dict и list. Уровень вложенности ограничен лишь реляционным движком, о причинах я расскажу позднее. Сам идентификатор объекта тоже может быть сохранен в другом (или в том же) объекте. Объекты можно создавать, удалять, и менять их содержимое произвольным образом; ну и, конечно же, искать нужные объекты по заданным критериям.

Итак, предположим, что вы уже скачали и установили модуль. Или даже просто представили это — не суть важно. В качестве иллюстрации работы с модулем я просто скопипащу сюда часть примеров из документации (для тех, кто поленился сходить по ссылке выше).

Итак, подключим модуль и создадим соединение. Для простоты будет использован дефолтный реляционный движок (SQLite) и in-memory база данных.

>>> import brain
>>> conn = brain.connect(None, None)


Теперь создадим пару объектов. Обратите внимание на вложенный список во втором объекте.

>>> id1 = conn.create({'a': 1, 'b': 1.345})
>>> id2 = conn.create({'id1': id1, 'list': [1, 2, 'some_value']})


Объекты можно прочитать целиком или выбрать конкретный участок. Во втором случае используется путь к нужным данным — просто список, где строка означает ключ в dict'е, а число — индекс в списке.

>>> print(conn.read(id1))
{'a': 1, 'b': 1.345}
>>> print(conn.read(id2, ['list']))
[1, 2, 'some_value']


Содержимое объекта можно изменить.

>>> conn.modify(id1, ['a'], 2)
>>> print(conn.read(id1))
{'a': 2, 'b': 1.345}


Ну и, наконец, нужный объект можно найти.

>>> import brain.op as op
>>> objs = conn.search(['list', 0], op.EQ, 1)
>>> print(objs == [id2])
True


Использованное условие расшифровывается как «0-й элемент списка, находящегося в ключе 'dict' корневого словаря, равен 1».

И это все?



Не совсем. Для списков существует специальная команда insert (работающая примерно так же, как и питоновский эквивалент). Объекты и их части можно удалять (в том числе и по маскам). Условия поиска могут комбинироваться при помощи логических операторов. Ах да, еще поддерживаются транзакции, есть простенький RPC-сервер и кэширующее соединение (довольно глупое). Обо всем этом можно прочитать в документации.

А что внутри?



Внутри все довольно просто. Для каждого уникального пути создается таблица с соответствующим именем (причем, пути, отличающиеся лишь индексами в списках, используют одну и ту же таблицу). Например, для данных вида {'key': [{'key2': 'val'}]}, значение 'val' будет храниться в таблице с именем «field.TEXT.key..key2» (пустое место между двумя точками говорит о том, что по ключу 'key' располагается список). Кроме того, существует одна большая таблица, в которой для каждого объекта записаны имеющиеся у него поля и их типы. Таким образом, если что-то вдруг пойдет не так, то данные вполне можно будет восстановить имеющимися средствами для работы с реляционными БД.

Отсюда проистекает ограничение на уровень вложенности хранимых структур данных — все зависит от того, какую максимальную длину имени таблицы поддерживает реляционная БД.

И что дальше?



Как я уже говорил, существующий вариант БД вполне достаточен для моих целей. Но если вдруг кто-то считает, что это творение может быть полезно кому-то еще, то я буду рад добавить необходимые фичи. Ну и, конечно же, приветствуется любая конструктивная критика по коду/архитектуре/документации.
Теги:
Хабы:
Всего голосов 4: ↑2 и ↓20
Комментарии6

Публикации

Ближайшие события