Многие методы, примененные в статье устарели и неактуальны. Обновленная версия.
Наверное уже все слышали модное нынче слово "nosql", кто-то возможно уже использовал на деле эти замечательные базы данных.
Haskell также имеет биндинги к различным nosql-бд, а именно:
- Redis
- Cassandra
- CouchDB
- Riak
- MongoDB
В данной статье я расскажу об использовании MongoDB и Haskell.
Прежде чем совершать какие-либо операции с БД, мы должны установить подключение. Делается это так:
pool <- newConnPool 1 $ host "127.0.0.1" -- или так pool <- newConnPool 1 $ Host "127.0.0.1" (PortNumber 3000)Функция newConnPool создает пул из TCP соединений. Но только при первом обращении, поэтому возникновение ошибки в этом месте невозможно.
Установив подключение, мы можем обращаться к базе данных при помощи Access-монады:
access safe Master pool action
Последним аргументом идет функция с возвращаемым типом Action m a. Пример такой функции:
action :: Action IO Cursor action = use (Database $ u "database_name") $ find (select [] "collection_name") {sort = ["_id" =: (1 :: Int)]}
Тип Cursor можно преобразовать в Document с помощью функции rest. Это можно делать сразу:
action :: Action IO [Document] action = use (Database $ u "database_name") $ rest =<< find (select [] "collection_name") {sort = ["_id" =: (1 :: Int)]}
Тогда на выходе мы получим список из Document, а далее будем извлекать из него значения с помощью библиотеки Data.Bson.
Немного забегая вперед. Для построения Bson-конструкций, мы должны включить OverloadedStrings language extension, а также применять функцию "u" к значениям в BSON конструкциях, для конвертации из String в UString.
{-# LANGUAGE OverloadedStrings #-}["key" =: u "value"]
Чуть выше мы уже применяли эту функцию в такой конструкции:
use (Database $ u "database_name")
Запрос информации из БД осуществляется с помощью совместного использования функций find и select:
find (select ["car" =: u"BMW"] "users_collection") {sort = ["_id" =: -1 :: Int], project = ["username" =: 1 :: Int, "age" =: 1 :: Int]}
Параметры в фигурных скобках - это поля из алгебраического типа Query. Запомнить следует лишь основные:
- project - какие поля включить в ответ. Грубо говоря, project = ["a" =: 1 :: Int, b =: 1 :: Int] равно sql-овскому "SELECT a, b".
- sort - сортировка. 1 - по возрастанию, -1 - по убыванию.
- skip - сколько документов пропустить. Заметьте, тип - Word32.
Остальные параметры будут использоваться редко, их посмотреть вы можете тут.
info = ["author" =: u"kreed", "blog" =: u"kreed131.blogspot.com", "message" =: u"Hello Mongo!"]access safe Master pool $ use (Database $ u "database") $ insert "collection" info
Как вы уже могли заметить, в названии статьи было что-то про простую жизнь.
По большей части наше "упрощение" заключается в том, чтобы убрать бессмысленные повторы одного и того же. Приступим!
Все это можно обернуть в модуль:
module Database.MongoDB.MyHelpers import Database.MongoDB
Функция для создания пула соединений (при использовании стандартного порта):
newPool h = newConnPool 1 $ host h
Определяем нашу базу данных и создаем функцию для её использования:
dbName = "our_db" withDb = use (Database $ u dbName)
Функция для запуска "экшенов" (мы её будем использовать как опорную для других функций):
run' p = access safe Master p . withDbФункция для получения информации из БД:
dbRecv pool a = run' pool (rest =<< a)
Функция для отправки данных в БД:
dbSend pool = run' pool
Функция, которую мы будем использовать подобно "u" в Bson-конструкциях:
i :: (Integral a) => a -> Inti = fromIntegral -- | w32 будет нужно для параметра skip. w32 :: (Integral a) => a -> Word32 w32 = fromIntegral
Т.е. вместо:
Мы будем писать:find (select ["target" =: u"car"] "transport") {sort = ["_id" =: -1 :: Int], project = ["msg" =: 1 :: Int, "target" =: 1 :: Int, "time" =: 1 :: Int, "_id" =: 1 :: Int]}
find (select ["target" =: u"car"] "transport") {sort = ["_id" =: i (-1)], project = ["msg" =: i 1, "target" =: i 1, "time" =: i 1, "_id" =: i 1]}
Удобно, правда? ;)
Тут вы должны были во весь голос кричать "нет"! Но вариантов у нас не было, по крайней мере я так думал.
Однако с подачи анонимного комментатора, мы можем забыть об этих функциях. Достаточно включить ExtendedDefaultRules:
Тут вы должны были во весь голос кричать "нет"! Но вариантов у нас не было, по крайней мере я так думал.
Однако с подачи анонимного комментатора, мы можем забыть об этих функциях. Достаточно включить ExtendedDefaultRules:
На этом все. Стоит лишь учесть, что из-за привязки к имени БД, эти функции нельзя назвать "общими".
Рад был бы увидеть как другие решают такие задачи, но увы, примеров с использованием MongoDB и Haskell почти нету в сети.
На этом все. Удачи!
Трэш, угар и содомия. Монго - это такая удобная штука, главная фича которой - об ней *не надо* думать. Поднял за 5 минут, взял удобный маппер и забыл навсегда.
ОтветитьУдалитьПисать
> find (select ["target" =: u"car"] "transport") {
> sort = ["_id" =: i (-1)]
> , project = ["msg" =: i 1, "target" =: i 1, "time" > =: i 1, "_id" =: i 1]
> }
ради одного селекта вменяемый человек никогда не станет, даже с унылой реляционной СУБД и голым HDBC это будет проще.
> Рад был бы увидеть как другие решают такие задачи
У Yesod-а был человеческий маппер к монге.
@anon
ОтветитьУдалитьВидимо плохо искал, но удобных mapper'ов, да и вообще mapper'ов не нашел. Благо это можно делать с помощью JavaScript.
Поискав, я нашел замечательную библиотеку helper'ов, для MongoDB:
https://github.com/MassiveTactical/mt-mongodb
Она использует Template Haskell и выглядит весьма симпатично.
If you use language extension "ExtendedDefaultRules" you don't need "u" or "i" converters. See http://github.com/TonyGen/mongoDB-haskell/blob/master/doc/Example.hs
ОтветитьУдалить@Anonymous,
ОтветитьУдалитьThank you! I corrected my post.