muesli-0.1.1.0: 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.

Functions whose name end in ' do the same operation as their counterparts, but return only the keys, without performing any I/O. They can be used to implement various kinds of joins not supported by the primitive operations.

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 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

data SortOrder Source

Sort order for range queries.

Constructors

SortAsc 
SortDesc 

range Source

Arguments

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

The page below.

-> Property a

The sortFld and table below.

-> Maybe (Sortable b)

The sortVal in the below SQL.

-> Maybe (Reference a)

The sortKey below.

-> SortOrder

The sortOrder 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 sortFld, ID sortOrder

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

Like range, but returns only the keys.

filterRange Source

Arguments

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

The page below.

-> Property a

The filterFld (and table) below.

-> Maybe (Reference c)

The filterVal below.

-> Property a

The sortFld (and table) below.

-> Maybe (Sortable b)

The sortVal below.

-> Maybe (Reference a)

The sortKey below.

-> SortOrder

The sortOrder 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 sortFld, ID sortOrder

filterRange' :: (Document a, ToKey (Sortable b), LogState l, MonadIO m) => Int -> Property a -> Maybe (Reference c) -> Property a -> Maybe (Sortable b) -> Maybe (Reference a) -> SortOrder -> Transaction l m [Reference a] Source

Like filterRange, but returns only the keys.

filter :: (Document a, LogState l, MonadIO m) => Property a -> Maybe (Reference b) -> Property a -> SortOrder -> Transaction l m [(Reference a, a)] Source

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

Like filterRange, but returns all documents, not just a range.

filter' :: (Document a, LogState l, MonadIO m) => Property a -> Maybe (Reference b) -> Property a -> SortOrder -> Transaction l m [Reference a] Source

Like filter, but returns only the keyes.

Queries on unique fields

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

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

unique' :: (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 unique' 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.