Несколько месяцев назад мне понадобилась документная база данных для одного планируемого проекта на 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 база данных.
Теперь создадим пару объектов. Обратите внимание на вложенный список во втором объекте.
Объекты можно прочитать целиком или выбрать конкретный участок. Во втором случае используется путь к нужным данным — просто список, где строка означает ключ в dict'е, а число — индекс в списке.
Содержимое объекта можно изменить.
Ну и, наконец, нужный объект можно найти.
Использованное условие расшифровывается как «0-й элемент списка, находящегося в ключе 'dict' корневого словаря, равен 1».
Не совсем. Для списков существует специальная команда insert (работающая примерно так же, как и питоновский эквивалент). Объекты и их части можно удалять (в том числе и по маскам). Условия поиска могут комбинироваться при помощи логических операторов. Ах да, еще поддерживаются транзакции, есть простенький RPC-сервер и кэширующее соединение (довольно глупое). Обо всем этом можно прочитать в документации.
Внутри все довольно просто. Для каждого уникального пути создается таблица с соответствующим именем (причем, пути, отличающиеся лишь индексами в списках, используют одну и ту же таблицу). Например, для данных вида {'key': [{'key2': 'val'}]}, значение 'val' будет храниться в таблице с именем «field.TEXT.key..key2» (пустое место между двумя точками говорит о том, что по ключу 'key' располагается список). Кроме того, существует одна большая таблица, в которой для каждого объекта записаны имеющиеся у него поля и их типы. Таким образом, если что-то вдруг пойдет не так, то данные вполне можно будет восстановить имеющимися средствами для работы с реляционными БД.
Отсюда проистекает ограничение на уровень вложенности хранимых структур данных — все зависит от того, какую максимальную длину имени таблицы поддерживает реляционная БД.
Как я уже говорил, существующий вариант БД вполне достаточен для моих целей. Но если вдруг кто-то считает, что это творение может быть полезно кому-то еще, то я буду рад добавить необходимые фичи. Ну и, конечно же, приветствуется любая конструктивная критика по коду/архитектуре/документации.
Где взять
Для самых нетерпеливых:
- страничка проекта с документацией и ссылкой для скачивания — pypi.python.org/pypi/brain
- git-репозиторий проекта — github.com/Manticore/brain
База данных выполнена в виде модуля и требует для использования 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' располагается список). Кроме того, существует одна большая таблица, в которой для каждого объекта записаны имеющиеся у него поля и их типы. Таким образом, если что-то вдруг пойдет не так, то данные вполне можно будет восстановить имеющимися средствами для работы с реляционными БД.
Отсюда проистекает ограничение на уровень вложенности хранимых структур данных — все зависит от того, какую максимальную длину имени таблицы поддерживает реляционная БД.
И что дальше?
Как я уже говорил, существующий вариант БД вполне достаточен для моих целей. Но если вдруг кто-то считает, что это творение может быть полезно кому-то еще, то я буду рад добавить необходимые фичи. Ну и, конечно же, приветствуется любая конструктивная критика по коду/архитектуре/документации.