Created
February 25, 2021 13:43
-
-
Save jxxcarlson/d24bb7d31ddc0dc2973afb6172ebd263 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{- | |
The idea behind module Db is to imitate part of the functionality of a | |
relation database which may have a number of indexes for fast searches. | |
In this example, the fundamental data structure is | |
type alias Db = | |
{ data : Dict Uuid Document | |
, authorDict : MultiBiDict Author Uuid | |
, titleDict : BiDict Title Uuid | |
, tagDict : MultiBiDict Tag Uuid | |
, pathDict : MultiBiDict String Uuid | |
, docNumberDict : BiDict Uuid Int | |
, keyWordAssociationList : List ( String, Uuid ) | |
} | |
where the primary store is under the data field and the other fields | |
point to indexes. The dictionary that data points to has Uuid's | |
as keys and Documents as values. The authorDict points to a | |
MultiBiDict that relates Authors of Documents with Uuids of Documents. | |
An Author will typically have many documents, and it is important | |
to be able to retrieve all of them on O(log n) time rather than in O(n) | |
time. Similarly with the other indexes. | |
It would be a huge pain and hugely unreliable to manage the indexes | |
separately. The idea is for the primitive operations such as insert | |
and remove to do all of this work for the consumer of the module. | |
The code below was written early on in the course of the project and | |
really needs to be reviewed. For example, the function | |
setDocument : Document -> Db -> Db | |
updates both the data and titleDict fields, but should also update | |
other fields. In fact, I generally use | |
insert : Document -> Db -> Db | |
which does everything. | |
-} | |
module Db exposing | |
( Db | |
, authorDocumentNumbers | |
, empty | |
, get | |
, getAll | |
, getByAuthor | |
, getByDocumentNumber | |
, getByTag | |
, getByTags | |
, getByTitle | |
, getPublic | |
, insert | |
, remove | |
, setContent | |
, setDocument | |
, setTag | |
, setTags | |
, setTitle | |
, tag | |
) | |
import BiDict exposing (BiDict) | |
import Dict exposing (Dict) | |
import Document exposing (Document) | |
import Maybe.Extra | |
import MultiBiDict exposing (MultiBiDict) | |
import Search.Fuzzy as Fuzzy | |
import Set | |
type alias Uuid = | |
String | |
type alias Author = | |
String | |
type alias Tag = | |
String | |
type alias Title = | |
String | |
type alias Content = | |
String | |
type alias Db = | |
{ data : Dict Uuid Document | |
, authorDict : MultiBiDict Author Uuid | |
, titleDict : BiDict Title Uuid | |
, tagDict : MultiBiDict Tag Uuid | |
, pathDict : MultiBiDict String Uuid | |
, docNumberDict : BiDict Uuid Int | |
, keyWordAssociationList : List ( String, Uuid ) | |
} | |
empty = | |
{ data = Dict.empty | |
, authorDict = MultiBiDict.empty | |
, titleDict = BiDict.empty | |
, tagDict = MultiBiDict.empty | |
, pathDict = MultiBiDict.empty | |
, docNumberDict = BiDict.empty | |
, keyWordAssociationList = [] | |
} | |
{-| | |
> db = insert row1 empty | |
-} | |
insert : Document -> Db -> Db | |
insert document db = | |
{ db | |
| data = Dict.insert document.id document db.data | |
, authorDict = MultiBiDict.insert document.author document.id db.authorDict | |
, titleDict = BiDict.insert (normalize document.title) document.id db.titleDict | |
, pathDict = MultiBiDict.insert document.path document.id db.pathDict | |
, docNumberDict = BiDict.insert document.id document.documentNumber db.docNumberDict | |
, keyWordAssociationList = Fuzzy.insert (Document.keyWords document) document.id db.keyWordAssociationList | |
} | |
tag : Tag -> Uuid -> Db -> Db | |
tag tag_ id db = | |
{ db | tagDict = MultiBiDict.insert tag_ id db.tagDict } | |
-- GETTERS | |
get : Db -> Uuid -> Maybe Document | |
get db uuid = | |
Dict.get uuid db.data | |
getAll : Db -> List Document | |
getAll db = | |
db.data |> Dict.toList |> List.map Tuple.second | |
getByAuthor : Db -> Author -> List Document | |
getByAuthor db author = | |
MultiBiDict.get author db.authorDict | |
|> Set.toList | |
|> List.map (get db) | |
|> Maybe.Extra.values | |
numberOfDocumentsForAuthor : Db -> Author -> Int | |
numberOfDocumentsForAuthor db author = | |
getByAuthor db author |> List.length | |
authorDocumentNumbers : Db -> Dict Author Int | |
authorDocumentNumbers db = | |
db.authorDict | |
|> MultiBiDict.keys | |
|> List.map (\author -> (author, numberOfDocumentsForAuthor db author)) | |
|> Dict.fromList | |
getByDocumentNumber : Db -> Int -> Maybe Document | |
getByDocumentNumber db docNumber = | |
let | |
maybeId = | |
BiDict.getReverse docNumber db.docNumberDict | |
|> Set.toList | |
|> List.head | |
in | |
case maybeId of | |
Nothing -> | |
Nothing | |
Just id -> | |
Dict.get id db.data | |
getPublic : Db -> List Document | |
getPublic db = | |
getAll db |> List.filter (\doc -> doc.public == True) | |
{-| | |
> getByTitle db "Intro to QM" | |
> Just { author = "jxxcarlson", content = "Yada yada", id = "123", tags = [], title = "Intro to QM" } | |
: Maybe (Row { content : String }) | |
-} | |
getByTitle : Db -> Title -> Maybe Document | |
getByTitle db title = | |
BiDict.get (normalize title) db.titleDict | |
|> Maybe.andThen (get db) | |
getByTag : Tag -> Db -> List Document | |
getByTag tag_ db = | |
MultiBiDict.get (normalize tag_) db.tagDict | |
|> Set.toList | |
|> List.map (get db) | |
|> Maybe.Extra.values | |
{-| | |
> getByTags ["qm", "atom"] mdb | |
[{ author = "jxxcarlson", content = "Yada yada", id = "234", tags = ["qm","atom"], title = "The Atom" }] | |
-} | |
getByTags : List Tag -> Db -> List Document | |
getByTags tagList db = | |
case List.head tagList of | |
Nothing -> | |
[] | |
Just firstTag -> | |
let | |
remainingItems : List Document | |
remainingItems = | |
getByTag firstTag db | |
remainingTags : List Tag | |
remainingTags = | |
List.drop 1 tagList | |
filter_ : Tag -> List Document -> List Document | |
filter_ tag_ items_ = | |
List.filter (\item_ -> List.member tag_ item_.tags) items_ | |
in | |
List.foldl filter_ remainingItems remainingTags | |
-- SETTERS | |
setTag : Db -> Uuid -> Tag -> Db | |
setTag db id_ tag_ = | |
{ db | tagDict = MultiBiDict.insert tag_ id_ db.tagDict } | |
setTagsInDocument : Uuid -> List Tag -> Db -> Db | |
setTagsInDocument id tags db = | |
case get db id of | |
Nothing -> | |
db | |
Just oldDocument -> | |
let | |
newDocument = | |
{ oldDocument | tags = tags } | |
in | |
{ db | data = Dict.insert id newDocument db.data } | |
setTags : Uuid -> List Tag -> Db -> Db | |
setTags id tags db = | |
List.foldl (\tag_ db_ -> setTag db_ id tag_) db tags | |
|> setTagsInDocument id tags | |
setTitle : Db -> Uuid -> String -> Db | |
setTitle db id title = | |
let | |
updater : Maybe Document -> Maybe Document | |
updater maybeRow = | |
case maybeRow of | |
Nothing -> | |
Nothing | |
Just row -> | |
Just { row | title = title } | |
in | |
{ db | data = Dict.update id updater db.data } |> resetTitleInIndex id title | |
resetTitleInIndex : Uuid -> String -> Db -> Db | |
resetTitleInIndex id title db = | |
case get db id of | |
Nothing -> | |
db | |
Just datum -> | |
{ db | |
| titleDict = BiDict.remove (normalize datum.title) db.titleDict |> BiDict.insert (normalize title) id | |
} | |
setContent : Db -> Uuid -> String -> Db | |
setContent db id content = | |
let | |
updater : Maybe Document -> Maybe Document | |
updater maybeDocument = | |
case maybeDocument of | |
Nothing -> | |
Nothing | |
Just document -> | |
Just { document | content = content } | |
in | |
{ db | data = Dict.update id updater db.data } | |
setDocument : Document -> Db -> Db | |
setDocument document db = | |
{ db | data = Dict.insert document.id document db.data | |
, titleDict = BiDict.insert (normalize document.title) document.id db.titleDict | |
} | |
-- DELETE | |
removeTitleFromIndex : BiDict Title Uuid -> Uuid -> BiDict Title Uuid | |
removeTitleFromIndex titleDict id = | |
let | |
maybeTitleToRemove = | |
BiDict.getReverse id titleDict |> Set.toList |> List.head | |
in | |
case maybeTitleToRemove of | |
Nothing -> | |
titleDict | |
Just title -> | |
BiDict.remove title titleDict | |
remove : Db -> Uuid -> Db | |
remove db id = | |
{ db | |
| data = Dict.remove id db.data | |
, titleDict = removeTitleFromIndex db.titleDict id | |
, docNumberDict = BiDict.remove id db.docNumberDict | |
} | |
-- |> List.map (Maybe.andThen (get db)) | |
-- HELPERS | |
normalize : String -> String | |
normalize str = | |
str | |
|> String.toLower | |
|> String.replace " " "_" | |
-- TESTS | |
emptyDoc = | |
Document.empty | |
doc1 = | |
{ emptyDoc | id = "abc", title = "First" } | |
doc2 = | |
{ emptyDoc | id = "def", title = "Second" } | |
dbx = | |
empty |> insert doc1 |> insert doc2 |> setTags "abc" [ "Yada " ] | |
dby = | |
setTags "abc" [ "foo", "bar" ] dbx | |
dbz = | |
setTags "def" [ "foo", "bax" ] dby |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment