muesli-0.1.0.1: A simple document-oriented database

Copyright(c) 2015 Călin Ardelean
LicenseMIT
MaintainerCălin Ardelean <calinucs@gmail.com>
Stabilityexperimental
Portabilityportable
Safe HaskellNone
LanguageHaskell2010
Extensions
  • ScopedTypeVariables
  • FlexibleContexts
  • TupleSections
  • ExplicitForAll

Database.Muesli.Query

Contents

Description

The Transaction monad and its primitive queries.

All queries in this module are run on indexes and perform an O(log n) worst case operation.

Synopsis

Documentation

The Transaction monad

data Transaction l m a Source

Abstract monad for writing and evaluating queries under ACID semantics.

The l parameter stands for a LogState backend, m is a MonadIO that gets lifted, so users can run arbitrary IO inside queries, and a is the result.

runQuery :: (MonadIO m, LogState l) => Handle l -> Transaction l m a -> m (Either TransactionAbort a) Source

Transaction evaluation inside a MonadIO.

Lookups are executed directly, targeting a specific version (TransactionId) , while the keys of both read and written documents are collected in the TransactionState.

At the end various consistency checks are performed, and the transaction is aborted in case any fails. See TransactionAbort for details.

Otherwise, under master lock, space in the data (abstract) file is allocated with alloc, and the transaction records are written in the logPend, and also in the log file, with logAppend.

Writing the serialized data to the data file, updating indexes and completing the transaction is then left for the commitThread. Note that transactions are only durable after commitThread finishes. In the future we may add a blocking version of runQuery.

data TransactionAbort Source

Error returned by runQuery for aborted transactions.

It is an instance of Exception solely for user convenience, as the database never throws it.

Constructors

AbortUnique String

Returned when trying to update an Unique field with a preexisting value.

AbortConflict String

Returned when there is a conflict with concurrent transactions. This happenes when the transUpdateList of transactions in logPend or logComp with transId > then our own transId has any keys that we also have in either transReadList, or transUpdateList.

The second part could be relaxed in the future based on a user policy, since overwriting updates are sometimes acceptable.

AbortDelete String

Returned when trying to delete a document that is still referenced by other documents.

TODO: Current implementation is not completely safe in this regard, as updates should also be checked. The reason is that the check is performed on indexes and on the pending log. So there is a small window in which it is possible for a concurrent transaction to update a record deleted by the current one, before adding it to the pending log, without any error.

Primitive queries

CRUD operations

lookup :: (Document a, LogState l, MonadIO m) => Reference a -> Transaction l m (Maybe (Reference a, a)) Source

Dereferences the given key. Returns Nothing if the key is not found.

insert :: (Document a, MonadIO m) => a -> Transaction l m (Reference a) Source

Inserts a new document and returns its key.

The primary key is generated with mkNewDocumentKey.

update :: forall a l m. (Document a, MonadIO m) => Reference a -> a -> Transaction l m () Source

Updates a document.

Note that since muesli is a MVCC database, this means inserting a new version of the document. The version number is the TransactionId of the current transaction. This fact is transparent to the user though.

delete :: MonadIO m => Reference a -> Transaction l m () Source

Deletes a document.

Note that since muesli is a MVCC database, this means inserting a new version with the recDeleted flag set to True. But this fact is transparent to the user, since the indexes are updated as if the record was really deleted.

It will be the job of the gcThread to actually clean the transaction log and compact the data file.

Range queries

range Source

Arguments

:: (Document a, ToKey (Sortable b), LogState l, MonadIO m) 
=> Maybe (Sortable b)

The sortVal in the below SQL.

-> Maybe (Reference a)

The sortKey below.

-> Property a

The sortFld and table below.

-> Int

The page below.

-> Transaction l m [(Reference a, a)] 

Runs a range query on a Sortable field.

It can be used as a cursor, for precise and efficient paging through a large dataset. For this purpose you should remember the last Reference from the previous page and give it as the sortKey argument below. This is needed since the sortable field may not have unique values, so remembering just the sortVal is insufficient.

The corresponding SQL is:

SELECT TOP page * FROM table
WHERE (sortVal = NULL OR sortFld < sortVal) AND (sortKey = NULL OR ID < sortKey)
ORDER BY field, ID DESC

rangeK :: (Document a, ToKey (Sortable b), MonadIO m) => Maybe (Sortable b) -> Maybe (Reference a) -> Property a -> Int -> Transaction l m [Reference a] Source

Like range, but only returns the keys and does not touch the data file. This may be used for implementing a faster deleteRange query, for example.

filter Source

Arguments

:: (Document a, ToKey (Sortable b), LogState l, MonadIO m) 
=> Maybe (Reference c)

The filterVal in the below SQL.

-> Maybe (Sortable b)

The sortVal below.

-> Maybe (Reference a)

The sortKey below.

-> Property a

The sortFld and table below.

-> Property a

The filterFld and table below.

-> Int

The page below.

-> Transaction l m [(Reference a, a)] 

Runs a filter-and-range query on a Reference field, with results sorted on a different Sortable field.

Sending Nothing for filterVal filters for NULL values, which correspond to a Nothing in a field of type Maybe (Reference a). This uses the special Maybe instance mentioned at the Indexable documentation.

The paging behaviour is the same as for range.

The corresponding SQL is:

SELECT TOP page * FROM table
WHERE (filterFld = filterVal) AND
      (sortVal = NULL OR sortFld < sortVal) AND (sortKey = NULL OR ID < sortKey)
ORDER BY field, ID DESC

Queries on unique fields

lookupUnique :: (ToKey (Unique b), MonadIO m) => Property a -> Unique b -> Transaction l m (Maybe (Reference a)) Source

Returns a Reference to a document uniquely determined by the given Unique key value, or Nothing if the key is not found.

updateUnique :: (Document a, ToKey (Unique b), MonadIO m) => Property a -> Unique b -> a -> Transaction l m (Reference a) Source

Performs a lookupUnique and then, depending whether the key exists or not, either inserts or updates the respective document.

Other

size :: (Document a, MonadIO m) => Property a -> Transaction l m Int Source

Returns the number of documents of type a in the database.