packages feed

TCache 0.8.0.2 → 0.9

raw patch · 8 files changed

+479/−127 lines, 8 filesdep +bytestringdep +textPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, text

API changes (from Hackage documentation)

- Data.TCache: class Indexable a
- Data.TCache: class Serializable a
- Data.TCache: defPath :: Indexable a => a -> String
- Data.TCache: defaultDelResource :: Indexable a => a -> IO ()
- Data.TCache: defaultReadResource :: (Serializable a, Indexable a, Typeable a) => a -> IO (Maybe a)
- Data.TCache: defaultReadResourceByKey :: (Serializable a, Indexable a) => String -> IO (Maybe a)
- Data.TCache: defaultWriteResource :: (Serializable a, Indexable a) => a -> IO ()
- Data.TCache: deserialize :: Serializable a => String -> a
- Data.TCache: key :: Indexable a => a -> String
- Data.TCache: readFileStrict :: FilePath -> IO [Char]
- Data.TCache: serialize :: Serializable a => a -> String
- Data.TCache.FilePersistence: instance (Serializable a, Indexable a) => IResource a
- Data.TCache.FilePersistence: instance (Show a, Read a) => Serializable a
- Data.TCache.IResource: class Indexable a
- Data.TCache.IResource: class Serializable a
- Data.TCache.IResource: defPath :: Indexable a => a -> String
- Data.TCache.IResource: defaultDelResource :: Indexable a => a -> IO ()
- Data.TCache.IResource: defaultReadResource :: (Serializable a, Indexable a, Typeable a) => a -> IO (Maybe a)
- Data.TCache.IResource: defaultReadResourceByKey :: (Serializable a, Indexable a) => String -> IO (Maybe a)
- Data.TCache.IResource: defaultWriteResource :: (Serializable a, Indexable a) => a -> IO ()
- Data.TCache.IResource: deserialize :: Serializable a => String -> a
- Data.TCache.IResource: key :: Indexable a => a -> String
- Data.TCache.IResource: serialize :: Serializable a => a -> String
- Data.TCache.IndexQuery: instance [incoherent] (IResource reg, Typeable reg, IResource reg', Typeable reg', Typeable a, Ord a, Read a, Show a, Read reg, Show reg, Read reg', Show reg', Serializable a) => RelationOps (reg -> a) (reg' -> a) (JoinData reg reg')
- Data.TCache.IndexQuery: instance [incoherent] (IResource reg, Typeable reg, Ord a, Read reg, Read a, Show reg, Show a) => Serializable (Index reg a)
- Data.TCache.IndexQuery: instance [incoherent] (Indexable reg, Typeable reg, Typeable a, Show reg, Ord a, Show a, Read a, IResource reg, Read reg) => RelationOps (reg -> a) a [DBRef reg]
- Data.TCache.IndexQuery: instance [incoherent] (Ord a, Read a, Typeable reg, IResource reg) => Read (Index reg a)
- Data.TCache.IndexQuery: instance [incoherent] (Typeable reg, Typeable a, Read reg, Show reg, Read a, Show a, Ord a, IResource reg) => IResource (Index reg a)
+ Data.TCache.DefaultPersistence: Persist :: (String -> IO (Maybe ByteString)) -> (String -> ByteString -> IO ()) -> (String -> IO ()) -> Persist
+ Data.TCache.DefaultPersistence: class Indexable a
+ Data.TCache.DefaultPersistence: class Serializable a | a ->
+ Data.TCache.DefaultPersistence: data Persist
+ Data.TCache.DefaultPersistence: defPath :: Indexable a => a -> String
+ Data.TCache.DefaultPersistence: delete :: Persist -> (String -> IO ())
+ Data.TCache.DefaultPersistence: deserialize :: Serializable a => ByteString -> a
+ Data.TCache.DefaultPersistence: instance (Typeable a, Indexable a, Serializable a) => IResource a
+ Data.TCache.DefaultPersistence: key :: Indexable a => a -> String
+ Data.TCache.DefaultPersistence: readByKey :: Persist -> (String -> IO (Maybe ByteString))
+ Data.TCache.DefaultPersistence: serialize :: Serializable a => a -> ByteString
+ Data.TCache.DefaultPersistence: setPersist :: Persist -> IO ()
+ Data.TCache.DefaultPersistence: write :: Persist -> (String -> ByteString -> IO ())
+ Data.TCache.IndexQuery: instance [incoherent] (IResource reg, Typeable reg, Read reg, Show reg, Show a, Read a, Ord a) => Read (Index reg a)
+ Data.TCache.IndexQuery: instance [incoherent] (Queriable reg a, Queriable reg' a) => RelationOps (reg -> a) (reg' -> a) (JoinData reg reg')
+ Data.TCache.IndexQuery: instance [incoherent] Queriable reg a => RelationOps (reg -> a) a [DBRef reg]
+ Data.TCache.IndexQuery: instance [incoherent] Queriable reg a => Serializable (Index reg a)
+ Data.TCache.IndexText: contains :: (IResource a, Typeable a, Typeable b) => (a -> b) -> String -> STM [DBRef a]
+ Data.TCache.IndexText: indexText :: (IResource a, Typeable a, Typeable b) => (a -> b) -> (b -> Text) -> IO ()
+ Data.TCache.IndexText: instance Indexable IndexText
+ Data.TCache.IndexText: instance Read IndexText
+ Data.TCache.IndexText: instance Serializable IndexText
+ Data.TCache.IndexText: instance Show IndexText
+ Data.TCache.IndexText: instance Typeable IndexText
- Data.TCache.IndexQuery: index :: (Typeable reg, Typeable a, Read reg, Show reg, Read a, Show a, Ord a, IResource reg, Indexable reg) => (reg -> a) -> IO ()
+ Data.TCache.IndexQuery: index :: Queriable reg a => (reg -> a) -> IO ()

Files

Data/TCache.hs view
@@ -141,11 +141,7 @@ {- | cached objects must be instances of IResource. Such instances can be implicitly derived trough auxiliary clasess for file persistence -}-,IResource(..)   -- class interface to be implemented for the object by the user
-,Serializable(..)
-,Indexable(..)--+,IResource(..)  -- * Operations with cached objects {- | Operations with DBRef's can be performed implicitly with the \"traditional\" TCache operations@@ -248,11 +244,6 @@ ,clearSyncCacheProc  
 ,defaultCheck -- * auxiliary file operations used for default persistence in files.
-,readFileStrict-,defaultReadResource                                                                  
-,defaultReadResourceByKey-,defaultWriteResource-,defaultDelResource
 )
 where 
 
@@ -268,20 +259,17 @@ import Data.TCache.Defs
 import Data.TCache.IResource import Data.TCache.Triggers
-import Control.Exception(handle,assert, SomeException)+import Control.Exception(handle,assert, bracket, SomeException) import Data.Typeable
 import System.Time import System.Mem import System.Mem.Weak-import Debug.Trace+--import Debug.Trace  import Control.Concurrent.MVar
  ---debug a b = trace b a-+--debug = flip trace  -- there are two references to the DBRef here -- The Maybe one keeps it alive until the cache releases it for *Resources@@ -483,11 +471,16 @@   mr <- readDBRef dbref   case mr of    Just x -> do-     safeIOToSTM $ delResource x      applyTriggers [dbref] [Nothing]      writeTVar tv DoNotExist++     safeIOToSTM $ bracket+       (takeMVar saving)+       (putMVar saving)+       $ const $ delResource x+    Nothing -> return ()-    --withSTMResources [] $ const  resources{toDelete=[x]}+     		
  @@ -535,11 +528,15 @@   case f mrs of
       Retry  -> retry
       Resources  as ds r  -> do-          safeIOToSTM $ mapM_ delResource ds           applyTriggers (map (getDBRef . keyResource) as) (map Just as)           applyTriggers (map (getDBRef . keyResource) ds) (repeat (Nothing  `asTypeOf` (Just(head ds))))           delListFromHash cache   ds
-          releaseTPVars as cache 
+          releaseTPVars as cache++          safeIOToSTM $ bracket+             (takeMVar saving)+             (putMVar saving)+             $ const $ mapM_ delResource ds
           return r
   
   where@@ -709,13 +706,17 @@ -- | Force the atomic write of all cached objects modified since the last save into permanent storage
 -- Cache writes allways save a coherent state
 syncCache ::  IO ()
-syncCache  = do-  (cache,lastSync) <- readIORef refcache-  t2<- timeInteger-  elems <- toList cache
-  (tosave,_,_) <- atomically $ extract elems lastSync
-  save tosave
-  writeIORef refcache (cache, t2) 
+syncCache  = bracket+  (takeMVar saving)+  (putMVar saving)+  $ const $ do+      (cache,lastSync) <- readIORef refcache  --`debug` "syncCache"+      t2<- timeInteger+      elems <- toList cache
+      (tosave,_,_) <- atomically $ extract elems lastSync
+      save tosave
+      writeIORef refcache (cache, t2)+  
 
 
 
@@ -724,14 +725,18 @@ --  delete some elems of  the cache when the number of elems > sizeObjects.
 --  The deletion depends on the check criteria. 'defaultCheck' is the one implemented
 clearSyncCache ::  (Integer -> Integer-> Integer-> Bool)-> Int -> IO ()
-clearSyncCache check sizeObjects=do-  (cache,lastSync) <- readIORef refcache-  t <- timeInteger-  elems <- toList cache-  (tosave, elems, size) <- atomically $ extract elems lastSync
-  save tosave  
-  when (size > sizeObjects) $  forkIO (filtercache t cache lastSync elems) >> performGC-  writeIORef refcache (cache, t)+clearSyncCache check sizeObjects= bracket+  (takeMVar saving)+  (putMVar saving)+  $ const $ do+      (cache,lastSync) <- readIORef refcache+      t <- timeInteger+      elems <- toList cache+      (tosave, elems, size) <- atomically $ extract elems lastSync
+      save tosave  
+      when (size > sizeObjects) $  forkIO (filtercache t cache lastSync elems) >> performGC+      writeIORef refcache (cache, t)+           
   where 
@@ -782,14 +787,13 @@  saving= unsafePerformIO $ newMVar False 
-save  tosave = do-     takeMVar saving
+save  tosave = do
      (pre, post) <-  readIORef refConditions
      pre      mapM (\(Filtered x) -> writeResource x) tosave
      post-     putMVar saving  False + data Filtered= forall a.(IResource a)=> Filtered a  @@ -817,7 +821,7 @@            
 safeIOToSTM :: IO a -> STM a-safeIOToSTM req= unsafeIOToSTM $  do+safeIOToSTM req= unsafeIOToSTM  $ do   tv   <- newEmptyMVar   forkIO $ req >>= putMVar  tv   takeMVar tv
+ Data/TCache/DefaultPersistence.hs view
@@ -0,0 +1,188 @@+{- |++This module provides default persistence , understood as retrievong and storing thje object in serialized blobs+for Indexable and serializable instances. The user can define it with setPersist. If the user does not+set it, persistence in files is used.+++-}+{-# LANGUAGE   FlexibleInstances, UndecidableInstances+               , MultiParamTypeClasses, FunctionalDependencies+               , ExistentialQuantification+                #-}+module Data.TCache.DefaultPersistence(Indexable(..),Serializable(..),setPersist,Persist(..)) where+
++import Data.TCache.IResource+import Data.Typeable+import System.IO.Unsafe+import Data.IORef+import System.Directory+import Control.Monad(when,replicateM)+import System.IO+import System.IO.Error+import Control.Exception as Exception+import Control.Concurrent+import Data.List(elemIndices,isInfixOf)+import Data.Maybe(fromJust)+import Data.TCache.Defs(castErr)+import qualified Data.ByteString.Lazy.Char8 as B++import Debug.Trace++--debug a b = trace b a++{- | Indexable is an utility class used to derive instances of IResource++Example:++@data Person= Person{ pname :: String, cars :: [DBRef Car]} deriving (Show, Read, Typeable)+data Car= Car{owner :: DBRef Person , cname:: String} deriving (Show, Read, Eq, Typeable)+@++Since Person and Car are instances of 'Read' ans 'Show', by defining the 'Indexable' instance+will implicitly define the IResource instance for file persistence:++@+instance Indexable Person where  key Person{pname=n} = \"Person \" ++ n+instance Indexable Car where key Car{cname= n} = \"Car \" ++ n+@+-}+class Indexable a where+    key:: a -> String+    defPath :: a -> String       -- ^ additional extension for default file paths.+                                -- The default value is "data/".++                                -- IMPORTANT:  defPath must depend on the datatype, not the value (must be constant). Default is "TCacheData/"
+    defPath =  const "TCacheData/"++--instance IResource a => Indexable a where+--   key x= keyResource x++{- | Serialize is an abstract serialization ionterface in order to define implicit instances of IResource.+The deserialization must be as lazy as possible if deserialized objects contain DBRefs,+lazy deserialization avoid unnecesary DBRef instantiations when they are not accessed,+since DBRefs instantiations involve extra cache lookups+For this reason serialization/deserialization is to/from ordinary Strings+serialization/deserialization are not performance critical in TCache++Read, Show,  instances are implicit instances of Serializable++>    serialize  = show+>    deserialize= read+-}+class Serializable a {-serialFormat-} | a -> {-serialFormat-} where+  serialize   :: a -> B.ByteString --serialFormat+  deserialize :: {-serialFormat-} B.ByteString -> a++{-+instance (Show a, Read a)=> Serializable a where+  serialize= show+  deserialize= read+-}+++-- |+data Persist = Persist{+       readByKey   ::  (String -> IO(Maybe B.ByteString)) -- ^  read+     , write       ::  (String -> B.ByteString -> IO())   -- ^  write+     , delete      ::  (String -> IO())}       -- ^  delete++defaultPersist= Persist+    {readByKey= defaultReadByKey+    ,write= defaultWrite+    ,delete= defaultDelete}+++persist :: IORef  Persist+persist = unsafePerformIO $ newIORef $  defaultPersist++-- | set an alternative persistence for Indexable and Serializable objects+setPersist ::  Persist -> IO ()+setPersist p = writeIORef persist   p+++instance  (Typeable a,  Indexable a, Serializable a ) => IResource a where++  keyResource = key+  writeResource s=do+      Persist _ f _ <- readIORef persist+      f (defPath s ++ key s) $ castErr $ serialize s++  readResourceByKey k= iox where+    iox= do+      Persist f _ _ <- readIORef persist+      f  file >>= return . fmap  deserialize . castErr+      where+      file= defPath x ++ k+      x= undefined `asTypeOf` (fromJust $ unsafePerformIO iox)++  delResource s= do+      Persist _ _ f <- readIORef persist+      f $ defPath s ++ key s+++++defaultReadByKey ::   String-> IO (Maybe B.ByteString)
+defaultReadByKey k= iox+     where+     iox = handle handler $ do   
+             s <-  readFileStrict  k 
+             return $ Just   s                                                       -- `debug` ("read "++ filename)
++ 
+     handler ::  IOError ->  IO (Maybe B.ByteString)
+     handler  e
+      | isAlreadyInUseError e = defaultReadByKey  k                         
+      | isDoesNotExistError e = return Nothing+      | otherwise= if ("invalid" `isInfixOf` ioeGetErrorString e)+         then+            error $ ( "readResource: " ++ show e) ++ " defPath and/or keyResource are not suitable for a file path"+              
+         else defaultReadByKey  k+++defaultWrite :: String-> B.ByteString -> IO()+defaultWrite filename x= safeWrite filename  x     --`debug` ("write "++filename)+safeWrite filename str= handle  handler  $ B.writeFile filename str+     where          
+     handler e-- (e :: IOError)
+       | isDoesNotExistError e=do 
+                  createDirectoryIfMissing True $ take (1+(last $ elemIndices '/' filename)) filename   --maybe the path does not exist
+                  safeWrite filename str               
++       | otherwise =do+                --phPutStrLn stderr $ "defaultWriteResource:  " ++ show e ++  " in file: " ++ filename ++ " retrying"
+                safeWrite filename str+              
+defaultDelete :: String -> IO()+defaultDelete filename =do+     removeFile filename+     --print  ("delete "++filename)
+     where+
+     handler :: String -> IOException -> IO ()
+     handler file e
+       | isDoesNotExistError e= return ()  --`debug` "isDoesNotExistError"+       | isAlreadyInUseError e= do+            hPutStrLn stderr $ "defaultDelResource: busy"  ++  " in file: " ++ filename ++ " retrying"+--            threadDelay 100000   --`debug`"isAlreadyInUseError"+            defaultDelete filename  
+       | otherwise = do+           hPutStrLn stderr $ "defaultDelResource:  " ++ show e ++  " in file: " ++ filename ++ " retrying"
+--           threadDelay 100000  --`debug` ("otherwise " ++ show e)+           defaultDelete filename+
++
+
+-- | Strict read from file, needed for default file persistence
+readFileStrict f = openFile f ReadMode >>= \ h -> readIt h `finally` hClose h+  where+  readIt h= do
+      s   <- hFileSize h
+      let n= fromIntegral s
+      str <- B.hGet h n -- replicateM n (B.hGetChar h) 
+      return str+
Data/TCache/Defs.hs view
@@ -17,3 +17,12 @@ type TPVar a=   TVar (Status(Elem a))  data DBRef a= DBRef String  (TPVar a)  deriving Typeable+++
+castErr a= r where+  r= case cast a of+      Nothing -> error $ "Type error: " ++ (show $ typeOf a) ++ " does not match "++ (show $ typeOf r)+                          ++ "\nThis means that objects of these two types have the same key \nor the retrieved object type is not the stored one for the same key\n"+      Just x  -> x+
− Data/TCache/FilePersistence.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE   FlexibleInstances, UndecidableInstances #-}-module Data.TCache.FilePersistence where-
--import Data.TCache.IResource----- | Read, Show,  instances are implicit instances of Serializable-instance (Show a, Read a) => Serializable a where-    serialize= show-    deserialize= read------- | Serializable, Indexable instances are implicit instances of IResource-instance  (Serializable a, Indexable a) => IResource a where-        keyResource =key-        readResourceByKey = defaultReadResourceByKey 
-        writeResource = defaultWriteResource
-    	delResource = defaultDelResource--
Data/TCache/IResource.hs view
@@ -78,7 +78,7 @@ resources =  Resources  [] [] ()
 
 -+{-  {- | Indexable is an utility class used to derive instances of IResource @@ -119,18 +119,15 @@   deserialize :: String -> a  +-} +++{- defaultReadResource :: (Serializable a, Indexable a, Typeable a) =>  a -> IO (Maybe a) defaultReadResource x= defaultReadResourceByKey $ key x -   
-castErr a= r where-  r= case cast a of-      Nothing -> error $ "Type error: " ++ (show $ typeOf a) ++ " does not match "++ (show $ typeOf r)-                          ++ "\nThis means that objects of these two types have the same key \nor the retrieved object type is not the stored one for the same key\n"-      Just x  -> x - defaultReadResourceByKey :: (Serializable a, Indexable a) =>  String-> IO (Maybe a)
 defaultReadResourceByKey k= iox      where@@ -202,3 +199,15 @@       return str
     
 +newtype Transient a= Transient a++-- | Transient wraps any indexable object for living in the cache, but not+-- in persistent storage. This is useful for memoization.+-- @Transient x@ is neither writeen nor read.+instance Indexable a => IResource (Transient a) where+   keyResource (Transient x)= key x+   readResourceByKey = const $ return Nothing+   writeResource= const $ return ()+   delResource = const $ return ()++-}
Data/TCache/IndexQuery.hs view
@@ -70,12 +70,13 @@  -} -{-# LANGUAGE DeriveDataTypeable, MultiParamTypeClasses+{-# LANGUAGE  DeriveDataTypeable, MultiParamTypeClasses , FunctionalDependencies, FlexibleInstances, UndecidableInstances-, TypeSynonymInstances, IncoherentInstances  #-}-module Data.TCache.IndexQuery(index,RelationOps(..), recordsWith, (.&&.), (.||.), Select(..)) where+, TypeSynonymInstances, IncoherentInstances #-}+module Data.TCache.IndexQuery(index, RelationOps(..), recordsWith, (.&&.), (.||.), Select(..)) where  import Data.TCache+import Data.TCache.DefaultPersistence import Data.List import Data.Typeable import Control.Concurrent.STM@@ -84,11 +85,25 @@ import Data.IORef import qualified  Data.Map as M import System.IO.Unsafe+import Data.ByteString.Lazy.Char8(pack, unpack)   -newtype Index reg a= Index (M.Map a [DBRef reg]) deriving (Read, Show, Typeable)+data Index reg a= Index (M.Map a [DBRef reg]) deriving (  Show, Typeable) +instance (IResource reg, Typeable reg,Read reg,Show reg, Show a, Read a, Ord a)+   => Read (Index reg a) where+  readsPrec n ('I':'n':'d':'e':'x':' ':str)+     = map (\(r,s) -> (Index r, s)) rs where rs= readsPrec n str+++class (Read reg, Read a, Show reg, Show a, IResource reg,Typeable reg, Typeable a,Ord a) => Queriable reg a++instance (Queriable reg a) => Serializable (Index reg a)  where+  serialize= pack . show+  deserialize= read . unpack++ keyIndex treg tv= "Index " ++ show treg ++ show tv  instance (Typeable reg, Typeable a) => Indexable (Index reg a) where@@ -97,6 +112,7 @@        [typeofreg, typeofa]= typeRepArgs $! typeOf map  +{- instance (IResource reg,Typeable reg, Ord a,Read reg, Read a, Show reg, Show a) => Serializable (Index reg a) where    serialize= show    deserialize= read@@ -130,15 +146,18 @@ persistIndex :: IORef (Maybe PersistIndex) persistIndex = unsafePerformIO $ newIORef Nothing --+-} +getIndex :: (Queriable reg a)+   => ( reg -> a) -> a -> STM(DBRef (Index reg a), Index reg a,[DBRef reg]) getIndex selector val= do    let [one, two]= typeRepArgs $! typeOf selector    let rindex= getDBRef $! keyIndex one two    getIndexr rindex val  +getIndexr :: (Queriable reg a)+   => DBRef(Index reg a) -> a -> STM(DBRef (Index reg a), Index reg a,[DBRef reg]) getIndexr rindex val= do    mindex <- readDBRef rindex @@ -151,15 +170,8 @@    return (rindex, Index index, dbrefs)  selectorIndex-  :: (Typeable reg,-      IResource reg,-      Typeable a,-      Read reg,-      Show reg,-      Read a,-      Show a,-      Ord a,-      Indexable reg) =>+  :: (Queriable reg a+      ) =>      (reg -> a) -> DBRef (Index reg a) -> DBRef reg -> Maybe reg -> STM ()  selectorIndex selector rindex pobject mobj = do@@ -193,15 +205,7 @@ -}  index-  :: (Typeable reg,-      Typeable a,-      Read reg,-      Show reg,-      Read a,-      Show a,-      Ord a,-      IResource reg,-      Indexable reg) =>+  :: (Queriable reg a) =>      (reg -> a) -> IO () index sel=    let [one, two]= typeRepArgs $! typeOf sel@@ -218,15 +222,7 @@  -- Instance of relations betweeen fields and values -- field .op. valued-instance (Indexable reg,-         Typeable reg,-         Typeable a,-         Show reg,-         Ord a,-         Show a,-         Read a,-         IResource reg,-         Read reg) => RelationOps (reg -> a) a  [DBRef reg] where+instance (Queriable reg a) => RelationOps (reg -> a) a  [DBRef reg] where     (.==.) field value= do        (_ ,_ ,dbrefs) <- getIndex field value        return dbrefs@@ -237,8 +233,7 @@     (.<=.)  field value= retrieve field value (<=)  -join:: (Typeable rec,IResource rec, Typeable v, Typeable rec', IResource rec', Read v,-       Show v, Read rec, Show rec, Ord v, Read rec', Show rec')+join:: (Queriable rec v, Queriable rec' v)        =>(v->v-> Bool) -> (rec -> v) -> (rec' -> v) -> STM[([DBRef rec], [DBRef rec'])] join op field1 field2 =do   idxs   <- retrieveIndexes field1@@ -254,15 +249,7 @@  -- Instance of relations betweeen fields -- field1 .op. field2-instance (IResource  reg,-         Typeable reg,-         IResource reg',-         Typeable reg',-         Typeable a,-         Ord a,-         Read a,-         Show a, Read reg, Show reg, Read reg', Show reg',-         Serializable a) =>RelationOps (reg -> a) (reg' -> a)  (JoinData reg reg') where+instance (Queriable reg a ,Queriable reg' a ) =>RelationOps (reg -> a) (reg' -> a)  (JoinData reg reg') where      (.==.)= join (==)     (.>.) = join (>)@@ -313,8 +300,9 @@      ys <- fys      return [(zs, union xs ys) | (zs,xs) <- xss] -retrieveIndexes :: (Typeable reg, Typeable a, Read a, Show a-                   , Read reg, Show reg, Ord a, IResource reg)+++retrieveIndexes :: (Queriable reg a )                    => (reg -> a) -> STM [(a,[DBRef reg])] retrieveIndexes selector= do    let [one, two]= typeRepArgs $! typeOf selector@@ -322,6 +310,7 @@    mindex <- readDBRef rindex    case mindex of Just (Index index) -> return $ M.toList index; _ -> return [] +retrieve :: Queriable reg a => (reg -> a) -> a -> (a -> a -> Bool) -> STM[DBRef reg] retrieve field value op= do    index <- retrieveIndexes field    let higuer = map (\(v, vals) -> if op v value then  vals else [])  index
+ Data/TCache/IndexText.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE TypeSynonymInstances+             , DeriveDataTypeable+             , FlexibleInstances+             , UndecidableInstances+             , MultiParamTypeClasses #-}+module Data.TCache.IndexText(indexText, contains) where++{-Implements full text indexation (`indexText`) and text search(`contains`), as an addition to+the query language implemented in `Data.TCache.IndexQuery`++An example:++@+data Doc= Doc{title, body :: String} deriving (Read,Show, Typeable)+instance Indexable Doc where+  key Doc{title=t}= t++instance Serializable Doc  where+  serialize= pack . show+  deserialize= read . unpack++main= do+  indexText  body T.pack+  let doc= Doc{title=  "title", body=  "hola que tal estamos"}+  rdoc <- atomically $ newDBRef doc+  r1 <- atomically $ select title $ body `contains` "hola que tal"+  print r1++  atomically $ writeDBRef rdoc  doc{ body=  "que tal"}+  r <- atomically $ select title $ body `contains` "hola que tal"+  print r+  if  r1 == [title doc] then print "OK" else print "FAIL"+  if  r== [] then print "OK" else print "FAIL"+@++-}+import Data.TCache+import Data.TCache.IndexQuery+import Data.TCache.DefaultPersistence+import qualified Data.Text.Lazy as T+import Data.Typeable+import qualified Data.Map as M+import Data.Maybe (catMaybes)+import Data.Bits+import System.Mem.StableName+import Data.List((\\))+import GHC.Conc(unsafeIOToSTM)+import Control.Concurrent(forkIO)+import Data.Char+import Control.Concurrent(threadDelay)+import Data.ByteString.Lazy.Char8(pack, unpack)++data IndexText=  IndexText+        { fieldType :: !String+        , lastDoc :: Int+        , mapDocKeyInt :: M.Map String Int+        , mapIntDocKey :: M.Map Int String+        , mapTextInteger :: M.Map T.Text Integer++         } deriving (Typeable)+++instance Show IndexText  where+   show (IndexText t a b c d)= show (t,a,b,c,d)++instance Read IndexText  where+  readsPrec n str= [(IndexText t a b c d, str2)| ((t,a,b,c,d),str2) <- readsPrec n str]++instance Serializable IndexText  where+  serialize= pack . show+  deserialize= read . unpack++instance  Indexable IndexText  where+   key (IndexText v _ _ _ _)=    "IndexText " ++ v+++readInitDBRef v x= do+  mv <- readDBRef x+  case mv of+    Nothing -> writeDBRef x v >> return v+    Just v -> return v++add ref t key w = op ref t setBit w key+del ref t key w = op ref t clearBit w key++op refIndex t set w key =  do+ if T.length w <3+  then return ()+  else do+   mindex <- readDBRef refIndex+   case mindex of+       Nothing -> writeDBRef refIndex $ IndexText t 0 (M.singleton key 0) (M.singleton  0 key) (M.singleton w 1)+       Just (IndexText t n mapSI mapIS map) -> do+        let (docLocation,n', mapSI')= case M.lookup key mapSI  of+               Nothing  -> let n'= n+1 in (n', n', M.insert key n' mapSI)  -- new Document+               Just m -> (m,n, mapSI)         -- already indexed document++        case M.lookup w map of+         Nothing ->    --new word+            writeDBRef refIndex $ IndexText t  n mapSI mapIS (M.insert w (set 0 docLocation) map)+         Just integer ->  -- word already indexed+               writeDBRef refIndex $ IndexText t n mapSI mapIS $ M.insert w (set integer docLocation) map++-- | start a trigger to index the contents of a register field+indexText+  :: (IResource a, Typeable a, Typeable b)+     => (a -> b)      -- ^ field to index+     -> (b -> T.Text) -- ^ method to convert the field content to Text (for example `pack` in case of String fields). This permits to index non Textual fields+     -> IO ()+indexText sel convert= addTrigger (indext sel refIndex convert) where+ [t1,t2]=  typeRepArgs $! typeOf sel+ t=  show t1 ++ show t2+ refIndex= getDBRef . key $ IndexText t u u u u where u= undefined+ indext :: (IResource a, Typeable a) => (a -> b) -> DBRef IndexText -> (b -> T.Text)  -> DBRef a -> Maybe a -> STM()+ indext sel refIndex convert dbref  mreg= f1 --  unsafeIOToSTM $! f+  where+  f=  forkIO (atomically f1) >> return()+  f1=  do+   moldreg <- readDBRef dbref+   case ( moldreg,  mreg) of+      (Nothing, Just reg)    -> mapM_ (add refIndex t (keyResource reg))    . words1 . convert $ sel reg+      (Just oldreg, Nothing) -> mapM_ (del refIndex t (keyResource oldreg)) . words1 . convert $ sel oldreg+      (Just oldreg, Just reg) -> do+        st  <- unsafeIOToSTM $ makeStableName $ sel oldreg -- test if field+        st' <- unsafeIOToSTM $ makeStableName $ sel reg    -- has changed+        if st== st'+          then return ()+          else do+            let key= keyResource reg+            let wrds = words1 . convert $ sel oldreg+            let wrds'= words1 . convert $ sel reg+            let new=  wrds' \\ wrds+            let old= wrds \\ wrds'+            mapM (del refIndex t key) old+            mapM (add refIndex t key) new+            return()++search :: (IResource a, Typeable a, Typeable b) => (a -> b)  -> T.Text -> STM [DBRef a]+search  sel w = do+    let [t1, t2]=  typeRepArgs $! typeOf sel+    let t=  show t1 ++ show t2+    let u= undefined+    mr <- withSTMResources [IndexText t u u u u]+       $ \[r] -> resources{toReturn= r}+    case mr of+      Nothing -> return []+      Just (IndexText t n _ mmapIntString map1) ->+       case M.lookup w map1 of+        Nothing ->  return []+        Just integer ->  do+            let mns=map (\n ->case testBit integer n of True -> Just n; _ -> Nothing)  [0..n]+            let wordsr = catMaybes $ map (\n -> M.lookup n mmapIntString) $ catMaybes mns+            return $ map getDBRef wordsr++words1= T.split (\c -> isSeparator c || c=='\n' || isPunctuation c )++-- | return the DBRefs whose fields include all the words of length three or more in the requested text contents+contains+  :: (IResource a, Typeable a, Typeable b)+  =>( a -> b)      -- ^ field to search in+   -> String       -- ^ text to search+   -> STM [DBRef a]+contains sel str= let text= T.pack str in case  T.words text of+     [] -> return []+     [w] -> search sel w+     ws  -> do+        let rs = map (search sel) $ filter (\t -> T.length t >2) ws+        foldl (.&&.) (head rs)  (tail rs)++++
TCache.cabal view
@@ -1,8 +1,10 @@ name:                TCache-version:             0.8.0.2-synopsis:            Data caching and Persistent STM transactions+version:             0.9+synopsis:            A Transactional cache with user-defined persistence description: +    This version now has full text search. Serialization is now trough byteStrings+     TCache is a transactional cache with configurable persitence. It allows conventional     STM transactions for objects that syncronize  with     their user defined storages. Default persistence in files is provided for testing purposes@@ -29,25 +31,27 @@     .     Now the file persistence is more reliable.The IO reads are safe inside STM transactions.     .-    To ease the implementation of other user-defined persistence, "Data.TCache.FIlePersistence" needed+    To ease the implementation of other user-defined persistence, "Data.TCache.DefaultPersistence" needed     to be imported  explcitly for deriving file persistence instances.     .-    In this release some stuff has been supressed without losing functionality. Dynamic interfaces-    are not needed since TCache can handle heterogeneous data.--+    The 0.9 version adds full text indexation and search, incorporated in the experimental query language+    .+    It also changes the default Persistence mechanism. Now ByteStrings are used for serialization and deserialization+    . a Serializable class and a Persist structure decouple serialization to/from bytestring+    and write-read to file. Both can be redefined separately, so default persistence can be changed with `setPersist`+    to write to blobs in databases, for example. Default persistence has'nt to be in files.  category:            Data, Database, Concurrency license:             BSD3 license-file:        LICENSE author:              Alberto Gómez Corona maintainer:          agocorona@gmail.com-Tested-With:         GHC == 7.0+Tested-With:         GHC == 7.0.3 Build-Type:          Simple-build-Depends:       base >=4 && <5,directory >= 1.0, old-time >=1.0,stm>=2, containers >= 0.1.0.1,  transformers >=0.2 && <0.3+build-Depends:       base >=4 && <5,directory >= 1.0, old-time >=1.0,stm>=2, containers >= 0.1.0.1,transformers >=0.2 && <0.3, text, bytestring  -exposed-modules:   Data.TCache, Data.TCache.FilePersistence-                   , Data.TCache.IndexQuery, Data.TCache.Triggers+exposed-modules:   Data.TCache, Data.TCache.DefaultPersistence+                   , Data.TCache.IndexQuery,Data.TCache.IndexText, Data.TCache.Triggers                    , Data.TCache.IResource, Data.TCache.Defs ghc-options:       -O2