packages feed

dynamodb-simple 0.2.0.0 → 0.3.0.0

raw patch · 14 files changed

+390/−182 lines, 14 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Database.DynamoDB: class (DynamoCollection a WithRange t, HasPrimaryKey a WithRange t) => CanQuery a (t :: TableType) hash range | a -> hash range
+ Database.DynamoDB: class ContainsTableKey a parent key | a -> parent key
+ Database.DynamoDB: class DynamoCollection a r IsIndex => DynamoIndex a parent (r :: RangeType) | a -> parent r
+ Database.DynamoDB: class (DynamoCollection a r IsTable, HasPrimaryKey a r IsTable) => DynamoTable a (r :: RangeType) | a -> r where dPutItem = defaultPutItem
+ Database.DynamoDB: class (DynamoCollection a r t, HasPrimaryKey a r t) => TableScan a (r :: RangeType) (t :: TableType)
+ Database.DynamoDB: innerJoin :: forall a b m r. (MonadAWS m, DynamoTable a r, Ord (PrimaryKey a r), ContainsTableKey a a (PrimaryKey a r)) => Consistency -> Proxy a -> (b -> Maybe (PrimaryKey a r)) -> Conduit [b] m [(b, a)]
+ Database.DynamoDB: leftJoin :: forall a b m r. (MonadAWS m, DynamoTable a r, Ord (PrimaryKey a r), ContainsTableKey a a (PrimaryKey a r)) => Consistency -> Proxy a -> (b -> Maybe (PrimaryKey a r)) -> Conduit [b] m [(b, Maybe a)]
+ Database.DynamoDB: queryOverIndex :: forall a t m v1 v2 hash r2 range rest parent. (CanQuery a t hash range, MonadAWS m, Code a ~ '[hash : (range : rest)], DynamoIndex a parent WithRange, ContainsTableKey a parent (PrimaryKey parent r2), DynamoTable parent r2, DynamoScalar v1 hash, DynamoScalar v2 range) => Proxy a -> QueryOpts a hash range -> Source m parent
+ Database.DynamoDB: querySourceChunks :: forall a t m hash range. (CanQuery a t hash range, MonadAWS m) => Proxy a -> QueryOpts a hash range -> Source m [a]
+ Database.DynamoDB: scanSourceChunks :: (MonadAWS m, TableScan a r t) => Proxy a -> ScanOpts a r -> Source m [a]
+ Database.DynamoDB: type PrimaryKey a r = PrimaryKey' (Code a) r
+ Database.DynamoDB.Types: instance Database.DynamoDB.Types.DynamoEncodable GHC.Int.Int16
+ Database.DynamoDB.Types: instance Database.DynamoDB.Types.DynamoEncodable GHC.Int.Int32
+ Database.DynamoDB.Types: instance Database.DynamoDB.Types.DynamoEncodable GHC.Int.Int64
+ Database.DynamoDB.Types: instance Database.DynamoDB.Types.DynamoScalar 'Network.AWS.DynamoDB.Types.Sum.N GHC.Int.Int16
+ Database.DynamoDB.Types: instance Database.DynamoDB.Types.DynamoScalar 'Network.AWS.DynamoDB.Types.Sum.N GHC.Int.Int32
+ Database.DynamoDB.Types: instance Database.DynamoDB.Types.DynamoScalar 'Network.AWS.DynamoDB.Types.Sum.N GHC.Int.Int64
- Database.DynamoDB: deleteItemBatchByKey :: forall m a r range hash rest. (MonadAWS m, HasPrimaryKey a r IsTable, DynamoTable a r, Code a ~ '[hash : (range : rest)]) => Proxy a -> [PrimaryKey (Code a) r] -> m ()
+ Database.DynamoDB: deleteItemBatchByKey :: forall m a r. (MonadAWS m, DynamoTable a r) => Proxy a -> [PrimaryKey a r] -> m ()
- Database.DynamoDB: deleteItemByKey :: forall m a r hash range rest. (MonadAWS m, HasPrimaryKey a r IsTable, DynamoTable a r, Code a ~ '[hash : (range : rest)]) => (Proxy a, PrimaryKey (Code a) r) -> m ()
+ Database.DynamoDB: deleteItemByKey :: forall m a r. (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> m ()
- Database.DynamoDB: deleteItemCondByKey :: forall m a r hash range rest. (MonadAWS m, HasPrimaryKey a r IsTable, DynamoTable a r, Code a ~ '[hash : (range : rest)]) => (Proxy a, PrimaryKey (Code a) r) -> FilterCondition a -> m ()
+ Database.DynamoDB: deleteItemCondByKey :: forall m a r. (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> FilterCondition a -> m ()
- Database.DynamoDB: getItem :: forall m a r range hash rest. (MonadAWS m, DynamoTable a r, HasPrimaryKey a r IsTable, Code a ~ '[hash : (range : rest)]) => Consistency -> (Proxy a, PrimaryKey (Code a) r) -> m (Maybe a)
+ Database.DynamoDB: getItem :: forall m a r. (MonadAWS m, DynamoTable a r) => Consistency -> Proxy a -> PrimaryKey a r -> m (Maybe a)
- Database.DynamoDB: getItemBatch :: forall m a r range hash rest. (MonadAWS m, DynamoTable a r, HasPrimaryKey a r IsTable, Code a ~ '[hash : (range : rest)]) => Consistency -> [PrimaryKey (Code a) r] -> m [a]
+ Database.DynamoDB: getItemBatch :: forall m a r. (MonadAWS m, DynamoTable a r) => Consistency -> [PrimaryKey a r] -> m [a]
- Database.DynamoDB: qConsistentRead :: forall a_aTMW hash_aTMX range_aTMY. Lens' (QueryOpts a_aTMW hash_aTMX range_aTMY) Consistency
+ Database.DynamoDB: qConsistentRead :: forall a_a150j hash_a150k range_a150l. Lens' (QueryOpts a_a150j hash_a150k range_a150l) Consistency
- Database.DynamoDB: qDirection :: forall a_aTMW hash_aTMX range_aTMY. Lens' (QueryOpts a_aTMW hash_aTMX range_aTMY) Direction
+ Database.DynamoDB: qDirection :: forall a_a150j hash_a150k range_a150l. Lens' (QueryOpts a_a150j hash_a150k range_a150l) Direction
- Database.DynamoDB: qFilterCondition :: forall a_aTMW hash_aTMX range_aTMY a_aU60. Lens (QueryOpts a_aTMW hash_aTMX range_aTMY) (QueryOpts a_aU60 hash_aTMX range_aTMY) (Maybe (FilterCondition a_aTMW)) (Maybe (FilterCondition a_aU60))
+ Database.DynamoDB: qFilterCondition :: forall a_a150j hash_a150k range_a150l a_a152W. Lens (QueryOpts a_a150j hash_a150k range_a150l) (QueryOpts a_a152W hash_a150k range_a150l) (Maybe (FilterCondition a_a150j)) (Maybe (FilterCondition a_a152W))
- Database.DynamoDB: qHashKey :: forall a_aTMW hash_aTMX range_aTMY. Lens' (QueryOpts a_aTMW hash_aTMX range_aTMY) hash_aTMX
+ Database.DynamoDB: qHashKey :: forall a_a150j hash_a150k range_a150l. Lens' (QueryOpts a_a150j hash_a150k range_a150l) hash_a150k
- Database.DynamoDB: qLimit :: forall a_aTMW hash_aTMX range_aTMY. Lens' (QueryOpts a_aTMW hash_aTMX range_aTMY) (Maybe Natural)
+ Database.DynamoDB: qLimit :: forall a_a150j hash_a150k range_a150l. Lens' (QueryOpts a_a150j hash_a150k range_a150l) (Maybe Natural)
- Database.DynamoDB: qRangeCondition :: forall a_aTMW hash_aTMX range_aTMY. Lens' (QueryOpts a_aTMW hash_aTMX range_aTMY) (Maybe (RangeOper range_aTMY))
+ Database.DynamoDB: qRangeCondition :: forall a_a150j hash_a150k range_a150l. Lens' (QueryOpts a_a150j hash_a150k range_a150l) (Maybe (RangeOper range_a150l))
- Database.DynamoDB: qStartKey :: forall a_aTMW hash_aTMX range_aTMY. Lens' (QueryOpts a_aTMW hash_aTMX range_aTMY) (Maybe (hash_aTMX, range_aTMY))
+ Database.DynamoDB: qStartKey :: forall a_a150j hash_a150k range_a150l. Lens' (QueryOpts a_a150j hash_a150k range_a150l) (Maybe (hash_a150k, range_a150l))
- Database.DynamoDB: query :: forall a t v1 v2 m range hash rest. (TableQuery a t, DynamoCollection a WithRange t, MonadAWS m, Code a ~ '[hash : (range : rest)], DynamoScalar v1 hash, DynamoScalar v2 range) => Proxy a -> QueryOpts a hash range -> Int -> m ([a], Maybe (PrimaryKey (Code a) WithRange))
+ Database.DynamoDB: query :: forall a t m range hash. (CanQuery a t hash range, MonadAWS m) => Proxy a -> QueryOpts a hash range -> Int -> m ([a], Maybe (PrimaryKey a WithRange))
- Database.DynamoDB: queryCond :: forall a t v1 v2 m hash range rest. (TableQuery a t, MonadAWS m, Code a ~ '[hash : (range : rest)], DynamoScalar v1 hash, DynamoScalar v2 range) => Proxy a -> hash -> Maybe (RangeOper range) -> FilterCondition a -> Direction -> Int -> m [a]
+ Database.DynamoDB: queryCond :: forall a t m hash range. (CanQuery a t hash range, MonadAWS m) => Proxy a -> hash -> Maybe (RangeOper range) -> FilterCondition a -> Direction -> Int -> m [a]
- Database.DynamoDB: querySimple :: forall a t v1 v2 m hash range rest. (TableQuery a t, MonadAWS m, Code a ~ '[hash : (range : rest)], DynamoScalar v1 hash, DynamoScalar v2 range) => Proxy a -> hash -> Maybe (RangeOper range) -> Direction -> Int -> m [a]
+ Database.DynamoDB: querySimple :: forall a t m hash range. (CanQuery a t hash range, MonadAWS m) => Proxy a -> hash -> Maybe (RangeOper range) -> Direction -> Int -> m [a]
- Database.DynamoDB: querySource :: forall a t m v1 v2 hash range rest. (TableQuery a t, MonadAWS m, Code a ~ '[hash : (range : rest)], DynamoScalar v1 hash, DynamoScalar v2 range) => Proxy a -> QueryOpts a hash range -> Source m a
+ Database.DynamoDB: querySource :: forall a t m hash range. (CanQuery a t hash range, MonadAWS m) => Proxy a -> QueryOpts a hash range -> Source m a
- Database.DynamoDB: sConsistentRead :: forall a_aU7s r_aU7t. Lens' (ScanOpts a_aU7s r_aU7t) Consistency
+ Database.DynamoDB: sConsistentRead :: forall a_a154q r_a154r. Lens' (ScanOpts a_a154q r_a154r) Consistency
- Database.DynamoDB: sFilterCondition :: forall a_aU7s r_aU7t. Lens' (ScanOpts a_aU7s r_aU7t) (Maybe (FilterCondition a_aU7s))
+ Database.DynamoDB: sFilterCondition :: forall a_a154q r_a154r. Lens' (ScanOpts a_a154q r_a154r) (Maybe (FilterCondition a_a154q))
- Database.DynamoDB: sLimit :: forall a_aU7s r_aU7t. Lens' (ScanOpts a_aU7s r_aU7t) (Maybe Natural)
+ Database.DynamoDB: sLimit :: forall a_a154q r_a154r. Lens' (ScanOpts a_a154q r_a154r) (Maybe Natural)
- Database.DynamoDB: sParallel :: forall a_aU7s r_aU7t. Lens' (ScanOpts a_aU7s r_aU7t) (Maybe (Natural, Natural))
+ Database.DynamoDB: sParallel :: forall a_a154q r_a154r. Lens' (ScanOpts a_a154q r_a154r) (Maybe (Natural, Natural))
- Database.DynamoDB: sStartKey :: forall a_aU7s r_aU7t r_aUIr. Lens (ScanOpts a_aU7s r_aU7t) (ScanOpts a_aU7s r_aUIr) (Maybe (PrimaryKey (Code a_aU7s) r_aU7t)) (Maybe (PrimaryKey (Code a_aU7s) r_aUIr))
+ Database.DynamoDB: sStartKey :: forall a_a154q r_a154r r_a15J7. Lens (ScanOpts a_a154q r_a154r) (ScanOpts a_a154q r_a15J7) (Maybe (PrimaryKey a_a154q r_a154r)) (Maybe (PrimaryKey a_a154q r_a15J7))
- Database.DynamoDB: scan :: (MonadAWS m, Code a ~ '[hash : (range : rest)], TableScan a r t, HasPrimaryKey a r t) => Proxy a -> ScanOpts a r -> Int -> m ([a], Maybe (PrimaryKey (Code a) r))
+ Database.DynamoDB: scan :: (MonadAWS m, TableScan a r t) => Proxy a -> ScanOpts a r -> Int -> m ([a], Maybe (PrimaryKey a r))
- Database.DynamoDB: scanCond :: forall a m hash range rest r t. (MonadAWS m, HasPrimaryKey a r t, Code a ~ '[hash : (range : rest)], TableScan a r t) => Proxy a -> FilterCondition a -> Int -> m [a]
+ Database.DynamoDB: scanCond :: forall a m r t. (MonadAWS m, TableScan a r t) => Proxy a -> FilterCondition a -> Int -> m [a]
- Database.DynamoDB: scanSource :: (MonadAWS m, TableScan a r t, HasPrimaryKey a r t, Code a ~ '[hash : (range : xss)]) => Proxy a -> ScanOpts a r -> Source m a
+ Database.DynamoDB: scanSource :: (MonadAWS m, TableScan a r t) => Proxy a -> ScanOpts a r -> Source m a
- Database.DynamoDB: tableKey :: forall a parent key. ContainsTableKey a parent key => a -> (Proxy parent, key)
+ Database.DynamoDB: tableKey :: forall a parent key. ContainsTableKey a parent key => a -> key
- Database.DynamoDB: updateItemByKey :: forall a m r hash range rest. (MonadAWS m, HasPrimaryKey a r IsTable, DynamoTable a r, Code a ~ '[hash : (range : rest)]) => (Proxy a, PrimaryKey (Code a) r) -> Action a -> m a
+ Database.DynamoDB: updateItemByKey :: forall a m r. (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> Action a -> m a
- Database.DynamoDB: updateItemByKey_ :: forall a m r hash range rest. (MonadAWS m, HasPrimaryKey a r IsTable, DynamoTable a r, Code a ~ '[hash : (range : rest)]) => (Proxy a, PrimaryKey (Code a) r) -> Action a -> m ()
+ Database.DynamoDB: updateItemByKey_ :: forall a m r. (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> Action a -> m ()
- Database.DynamoDB: updateItemCond_ :: forall a m r hash range rest. (MonadAWS m, DynamoTable a r, HasPrimaryKey a r IsTable, Code a ~ '[hash : (range : rest)]) => (Proxy a, PrimaryKey (Code a) r) -> Action a -> FilterCondition a -> m ()
+ Database.DynamoDB: updateItemCond_ :: forall a m r. (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> FilterCondition a -> Action a -> m ()
- Database.DynamoDB.Types: class (ScalarAuto v, DynamoEncodable a) => DynamoScalar (v :: ScalarAttributeType) a | a -> v where scalarEncode = ScS . pack . show scalarDecode (ScS txt) = readMaybe (unpack txt)
+ Database.DynamoDB.Types: class ScalarAuto v => DynamoScalar (v :: ScalarAttributeType) a | a -> v where scalarEncode = ScS . pack . show scalarDecode (ScS txt) = readMaybe (unpack txt)

Files

README.md view
@@ -30,7 +30,7 @@       --       putItem (Test "news" "john" "test" 20)       ---      item <- getItem Eventually (tTest, ("news", "john"))+      item <- getItem Eventually tTest ("news", "john")       liftIO $ print item       --       items <- scanCond tTest (replies' >. 15) 10@@ -56,7 +56,9 @@   queries when searching for these empty values. - Compatible with GHC8 `DuplicateRecordFields` - Customizable table and index names. Custom translation of field names to attribute names.-- Streaming settings.+- AWS Dynamo streaming settings.+- Utilities to help with simulated joins or retriving original data from index.+- Both conduit and page-based API.  ### What is planned 
changelog.md view
@@ -1,3 +1,10 @@+# 0.3.0.0+- API changes regarding position of `Proxy`+- Added index->table conversion functions+- Added conduits for left/inner join+- Added queryOverIndex+- Simplification of exposed function signatures+ # 0.2.0.0  - Changed API to always include a `Proxy`
dynamodb-simple.cabal view
@@ -1,5 +1,5 @@ name:                dynamodb-simple-version:             0.2.0.0+version:             0.3.0.0 synopsis:            Typesafe library for working with DynamoDB database description:         Framework for accessing DynamoDB database. The majority of AWS API                      is available to the user in a convenient, simple and typesafe manner.@@ -26,7 +26,7 @@   other-modules:       Database.DynamoDB.Class, Database.DynamoDB.Migration,                        Database.DynamoDB.Internal, Database.DynamoDB.BatchRequest,                        Database.DynamoDB.QueryRequest, Database.DynamoDB.THLens,-                       Database.DynamoDB.THContains+                       Database.DynamoDB.THContains, Database.DynamoDB.THConvert   build-depends:       base >=4.8 && <5, amazonka-dynamodb, generics-sop,                        unordered-containers, text, lens, double-conversion,                        semigroups, bytestring, containers, monad-supply,
src/Database/DynamoDB.hs view
@@ -26,6 +26,9 @@     -- * Lens support     -- $lens +    -- * Conversion support+    -- $conversion+     -- * Data types     DynamoException(..)   , Consistency(..)@@ -45,6 +48,8 @@   , querySimple   , queryCond   , querySource+  , querySourceChunks+  , queryOverIndex     -- * Scan options   , ScanOpts   , scanOpts@@ -52,7 +57,11 @@     -- * Performing scan   , scan   , scanSource+  , scanSourceChunks   , scanCond+    -- * Helper conduits+  , leftJoin+  , innerJoin     -- * Data entry   , putItem   , putItemBatch@@ -69,6 +78,13 @@   , deleteTable     -- * Utility functions   , tableKey+    -- * Typeclasses+  , DynamoTable+  , DynamoIndex+  , PrimaryKey+  , ContainsTableKey+  , CanQuery+  , TableScan ) where  import           Control.Lens                        ((%~), (.~), (^.))@@ -79,7 +95,6 @@ import           Data.Proxy import           Data.Semigroup                      ((<>)) import qualified Data.Text                           as T-import           Generics.SOP import           Network.AWS import qualified Network.AWS.DynamoDB.DeleteItem     as D import qualified Network.AWS.DynamoDB.GetItem        as D@@ -97,12 +112,10 @@ import           Database.DynamoDB.QueryRequest  -dDeleteItem :: (DynamoTable a r, HasPrimaryKey a r 'IsTable, Code a ~ '[ hash ': range ': xss ])-          => Proxy a -> PrimaryKey (Code a) r -> D.DeleteItem+dDeleteItem :: DynamoTable a r => Proxy a -> PrimaryKey a r -> D.DeleteItem dDeleteItem p pkey = D.deleteItem (tableName p) & D.diKey .~ dKeyToAttr p pkey -dGetItem :: (DynamoTable a r, HasPrimaryKey a r 'IsTable, Code a ~ '[ hash ': range ': xss ])-          => Proxy a -> PrimaryKey (Code a) r -> D.GetItem+dGetItem :: DynamoTable a r => Proxy a -> PrimaryKey a r -> D.GetItem dGetItem p pkey = D.getItem (tableName p) & D.giKey .~ dKeyToAttr p pkey  -- | Write item into the database; overwrite any previously existing item with the same primary key.@@ -123,31 +136,26 @@   -- | Read item from the database; primary key is either a hash key or (hash,range) tuple depending on the table.-getItem :: forall m a r range hash rest.-    (MonadAWS m, DynamoTable a r, HasPrimaryKey a r 'IsTable, Code a ~ '[ hash ': range ': rest])-    => Consistency -> (Proxy a, PrimaryKey (Code a) r) -> m (Maybe a)-getItem consistency (p, key) = do+getItem :: forall m a r. (MonadAWS m, DynamoTable a r) => Consistency -> Proxy a -> PrimaryKey a r -> m (Maybe a)+getItem consistency p key = do   let cmd = dGetItem p key & D.giConsistentRead . consistencyL .~ consistency   rs <- send cmd   let result = rs ^. D.girsItem   if | null result -> return Nothing      | otherwise ->-          case gsDecode result of+          case dGsDecode result of               Just res -> return (Just res)               Nothing -> throwM (DynamoException $ "Cannot decode item: " <> T.pack (show result))  -- | Delete item from the database by specifying the primary key.-deleteItemByKey :: forall m a r hash range rest.-    (MonadAWS m, HasPrimaryKey a r 'IsTable, DynamoTable a r, Code a ~ '[ hash ': range ': rest])-    => (Proxy a, PrimaryKey (Code a) r) -> m ()-deleteItemByKey (p, pkey) = void $ send (dDeleteItem p pkey)+deleteItemByKey :: forall m a r. (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> m ()+deleteItemByKey p pkey = void $ send (dDeleteItem p pkey)  -- | Delete item from the database by specifying the primary key and a condition. -- Throws AWS exception if the condition does not succeed.-deleteItemCondByKey :: forall m a r hash range rest.-    (MonadAWS m, HasPrimaryKey a r 'IsTable, DynamoTable a r, Code a ~ '[ hash ': range ': rest])-    => (Proxy a, PrimaryKey (Code a) r) -> FilterCondition a -> m ()-deleteItemCondByKey (p, pkey) cond =+deleteItemCondByKey :: forall m a r.+    (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> FilterCondition a -> m ()+deleteItemCondByKey p pkey cond =     let (expr, attnames, attvals) = dumpCondition cond         cmd = dDeleteItem p pkey & D.diExpressionAttributeNames .~ attnames                                  & bool (D.diExpressionAttributeValues .~ attvals) id (null attvals) -- HACK; https://github.com/brendanhay/amazonka/issues/332@@ -156,9 +164,8 @@  -- | Generate update item object; automatically adds condition for existence of primary -- key, so that only existing objects are modified-dUpdateItem :: forall a r hash range xss.-            (DynamoTable a r, HasPrimaryKey a r 'IsTable, Code a ~ '[ hash ': range ': xss ])-          => Proxy a -> PrimaryKey (Code a) r -> Action a -> Maybe (FilterCondition a) ->  Maybe D.UpdateItem+dUpdateItem :: forall a r. DynamoTable a r+          => Proxy a -> PrimaryKey a r -> Action a -> Maybe (FilterCondition a) ->  Maybe D.UpdateItem dUpdateItem p pkey actions mcond =     genAction <$> dumpActions actions   where@@ -183,50 +190,48 @@     addCondition Nothing = id -- Cannot happen anyway  --- | Update item in a table+-- | Update item in a table. ----- > updateItem (Proxy :: Proxy Test) (12, "2") [colCount +=. 100]-updateItemByKey_ :: forall a m r hash range rest.-      (MonadAWS m, HasPrimaryKey a r 'IsTable, DynamoTable a r, Code a ~ '[ hash ': range ': rest ])-    => (Proxy a, PrimaryKey (Code a) r) -> Action a -> m ()-updateItemByKey_ (p, pkey) actions+-- > updateItem (Proxy :: Proxy Test) (12, "2") (colCount +=. 100)+updateItemByKey_ :: forall a m r.+      (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> Action a -> m ()+updateItemByKey_ p pkey actions   | Just cmd <- dUpdateItem p pkey actions Nothing = void $ send cmd   | otherwise = return () -updateItemByKey :: forall a m r hash range rest.-      (MonadAWS m, HasPrimaryKey a r 'IsTable, DynamoTable a r, Code a ~ '[ hash ': range ': rest ])-    => (Proxy a, PrimaryKey (Code a) r) -> Action a -> m a-updateItemByKey (p, pkey) actions+-- | Update item in a database, return an updated version of the item.+updateItemByKey :: forall a m r.+      (MonadAWS m, DynamoTable a r) => Proxy a -> PrimaryKey a r -> Action a -> m a+updateItemByKey p pkey actions   | Just cmd <- dUpdateItem p pkey actions Nothing = do         rs <- send (cmd & D.uiReturnValues .~ Just D.AllNew)-        case gsDecode (rs ^. D.uirsAttributes) of+        case dGsDecode (rs ^. D.uirsAttributes) of             Just res -> return res             Nothing -> throwM (DynamoException $ "Cannot decode item: " <> T.pack (show rs))   | otherwise = do-      rs <- getItem Strongly (p, pkey)+      rs <- getItem Strongly p pkey       case rs of           Just res -> return res           Nothing -> throwM (DynamoException "Cannot decode item.") --- | Update item in a table while specifying a condition-updateItemCond_ :: forall a m r hash range rest.-      (MonadAWS m, DynamoTable a r, HasPrimaryKey a r 'IsTable, Code a ~ '[ hash ': range ': rest ])-    => (Proxy a, PrimaryKey (Code a) r) -> Action a -> FilterCondition a -> m ()-updateItemCond_ (p, pkey) actions cond+-- | Update item in a table while specifying a condition.+updateItemCond_ :: forall a m r. (MonadAWS m, DynamoTable a r)+    => Proxy a -> PrimaryKey a r -> FilterCondition a -> Action a -> m ()+updateItemCond_ p pkey cond actions   | Just cmd <- dUpdateItem p pkey actions (Just cond) = void $ send cmd   | otherwise = return () --- | Delete table from DynamoDB.+-- | Delete a table from DynamoDB. deleteTable :: (MonadAWS m, DynamoTable a r) => Proxy a -> m () deleteTable p = void $ send (D.deleteTable (tableName p)) --- | Extract primary key from a record in a form that can be directly used by other functions.+-- | Extract primary key from a record. -- -- You can use this on both main table or on index tables if they contain the primary key from -- the main table. Table key is always projected to indexes anyway, so just define it in -- every index.-tableKey :: forall a parent key. ContainsTableKey a parent key => a -> (Proxy parent, key)-tableKey a = (Proxy, dTableKey a)+tableKey :: forall a parent key. ContainsTableKey a parent key => a -> key+tableKey = dTableKey  -- $intro --@@ -276,7 +281,7 @@ --        -- Save data to database --        putItem (Test "news" "1-2-3-4" "New subject") --        -- Fetch data given primary key---        item <- getItem Eventually (tTest, ("news", "1-2-3-4"))+--        item <- getItem Eventually tTest ("news", "1-2-3-4") --        liftIO $ print item       -- (item :: Maybe Test) --        -- Scan data using filter condition, return 10 results --        items <- scanCond tTest (subject' ==. "New subejct") 10@@ -307,8 +312,8 @@ --   , _subject :: T.Text -- } deriving (Show) -- data TestIndex = TestIndex {---     _category :: T.Text---   , _messageid :: T.Text+--     i_category :: T.Text+--   , i_messageid :: T.Text -- } -- mkTableDefs "migrate" (tableConfig (''Test, WithRange) [(''TestIndex, NoRange)] []) --@@ -318,3 +323,10 @@ -- doWithItemIdx :: TestIndex -> .. -- getCategoryIdx item = (item ^. category) ... -- @++-- $conversion+--+-- Given a type 'Test' and an index type 'TestIndex',+-- a function 'toTest' is created that converts from 'TestIndex' to 'Test'.+-- Such function is created only if 'TestIndex' projects+-- all fields from 'Test'.
src/Database/DynamoDB/BatchRequest.hs view
@@ -25,7 +25,6 @@ import           Data.Monoid                         ((<>)) import           Data.Proxy import qualified Data.Text                           as T-import           Generics.SOP import           Network.AWS import qualified Network.AWS.DynamoDB.BatchGetItem   as D import qualified Network.AWS.DynamoDB.BatchWriteItem as D@@ -86,9 +85,7 @@   -- | Get batch of items.-getItemBatch :: forall m a r range hash rest.-    (MonadAWS m, DynamoTable a r, HasPrimaryKey a r 'IsTable, Code a ~ '[ hash ': range ': rest])-    => Consistency -> [PrimaryKey (Code a) r] -> m [a]+getItemBatch :: forall m a r. (MonadAWS m, DynamoTable a r) => Consistency -> [PrimaryKey a r] -> m [a] getItemBatch consistency lst = concat <$> mapM go (chunkBatch 100 lst)   where     go keys = do@@ -100,21 +97,18 @@         tbls <- retryReadBatch cmd         mapM decoder (tbls ^.. ix tblname . traverse)     decoder item =-        case gsDecode item of+        case dGsDecode item of           Just res -> return res           Nothing -> throwM (DynamoException $ "Error decoding item: " <> T.pack (show item)) -dDeleteRequest :: (HasPrimaryKey a r 'IsTable, Code a ~ '[ hash ': range ': xss ])-          => Proxy a -> PrimaryKey (Code a) r -> D.DeleteRequest+dDeleteRequest :: DynamoTable a r => Proxy a -> PrimaryKey a r -> D.DeleteRequest dDeleteRequest p pkey = D.deleteRequest & D.drKey .~ dKeyToAttr p pkey  -- | Batch version of 'deleteItemByKey'. -- -- Note: Because the requests are chunked, the information about which items -- were deleted in case of exception is unavailable.-deleteItemBatchByKey :: forall m a r range hash rest.-    (MonadAWS m, HasPrimaryKey a r 'IsTable, DynamoTable a r, Code a ~ '[ hash ': range ': rest])-    => Proxy a -> [PrimaryKey (Code a) r] -> m ()+deleteItemBatchByKey :: forall m a r. (MonadAWS m, DynamoTable a r) => Proxy a -> [PrimaryKey a r] -> m () deleteItemBatchByKey p lst = mapM_ go (chunkBatch 25 lst)   where     go keys = do
src/Database/DynamoDB/Class.hs view
@@ -5,7 +5,6 @@ {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} -- We have lots of pattern matching for allFieldNames, that is correct because of TH {-# LANGUAGE DataKinds              #-}-{-# LANGUAGE DefaultSignatures      #-} {-# LANGUAGE FlexibleContexts       #-} {-# LANGUAGE FlexibleInstances      #-} {-# LANGUAGE FunctionalDependencies #-}@@ -19,6 +18,7 @@ {-# LANGUAGE TypeFamilies           #-} {-# LANGUAGE TypeOperators          #-} {-# LANGUAGE UndecidableInstances   #-}+{-# LANGUAGE DefaultSignatures   #-} #if __GLASGOW_HASKELL__ >= 800 {-# LANGUAGE UndecidableSuperClasses #-} #endif@@ -27,7 +27,6 @@     DynamoCollection(..)   , DynamoTable(..)   , DynamoIndex(..)-  , dQueryKey   , dScan   , RangeType(..)   , TableType(..)@@ -36,7 +35,7 @@   , gsEncode   , gsEncodeG   , PrimaryKey-  , TableQuery+  , CanQuery(..)   , TableScan   , TableCreate(..)   , IndexCreate(..)@@ -80,18 +79,23 @@   => DynamoCollection a (r :: RangeType) (t :: TableType) | a -> r t where   allFieldNames :: Proxy a -> [T.Text]   primaryFields :: Proxy a -> [T.Text]+  dGsDecode :: HashMap T.Text AttributeValue -> Maybe a+  default dGsDecode :: (Code a ~ '[ xs ]) => HashMap T.Text AttributeValue -> Maybe a+  dGsDecode = gsDecode  class DynamoCollection a r t => HasPrimaryKey a (r :: RangeType) (t :: TableType) where-  dItemToKey :: (Code a ~ '[ hash ': range ': xss ]) => a -> PrimaryKey (Code a) r-  dKeyToAttr :: (Code a ~ '[ hash ': range ': xss ])-            => Proxy a -> PrimaryKey (Code a) r -> HMap.HashMap T.Text D.AttributeValue-  dAttrToKey :: Proxy a -> HMap.HashMap T.Text D.AttributeValue -> Maybe (PrimaryKey (Code a) r)+  dItemToKey :: a -> PrimaryKey a r+  dKeyToAttr :: Proxy a -> PrimaryKey a r -> HMap.HashMap T.Text D.AttributeValue+  dAttrToKey :: Proxy a -> HMap.HashMap T.Text D.AttributeValue -> Maybe (PrimaryKey a r)+  -- | Return True if key is well-defined (i.e. no empty string, no Maybe etc.)+  dKeyIsDefined :: Proxy a -> PrimaryKey a r -> Bool  instance (DynamoCollection a 'NoRange t, Code a ~ '[ hash ': xss ],           DynamoScalar v hash) => HasPrimaryKey a 'NoRange t where   dItemToKey = gdFirstField   dKeyToAttr p key = HMap.singleton (head $ allFieldNames p) (dScalarEncode key)   dAttrToKey p attrs = HMap.lookup (head $ allFieldNames p) attrs >>= dDecode . Just+  dKeyIsDefined _ = not . dIsMissing  instance (DynamoCollection a 'WithRange t, Code a ~ '[ hash ': range ': xss ],           DynamoScalar v1 hash, DynamoScalar v2 range) => HasPrimaryKey a 'WithRange t where@@ -107,9 +111,10 @@       rngkey <- HMap.lookup rangename attrs       rngval <- dDecode (Just rngkey)       return (pval, rngval)+  dKeyIsDefined _ (hash, range) = not (dIsMissing hash) && not (dIsMissing range)  -- | Descritpion of dynamo table-class DynamoCollection a r 'IsTable => DynamoTable a (r :: RangeType) | a -> r where+class (DynamoCollection a r 'IsTable, HasPrimaryKey a r 'IsTable) => DynamoTable a (r :: RangeType) | a -> r where   -- | Dynamo table/index name; default is the constructor name   tableName :: Proxy a -> T.Text   -- | Serialize data, put it into the database@@ -125,47 +130,54 @@     => TableCreate a 'WithRange where   createTable = defaultCreateTableRange --- | Instance for tables that can be queried-class DynamoCollection a 'WithRange t => TableQuery a (t :: TableType) where+-- | Class for tables/indexes that can be queried+class (DynamoCollection a 'WithRange t, HasPrimaryKey a 'WithRange t) => CanQuery a (t :: TableType) hash range | a -> hash range where   -- | Return table name and index name   qTableName :: Proxy a -> T.Text   qIndexName :: Proxy a -> Maybe T.Text   -- | Create a query using both hash key and operation on range key   -- On tables without range key, this degrades to queryKey-  dQueryKey :: (Code a ~ '[ hash ': range ': rest ])-      => Proxy a -> hash -> Maybe (RangeOper range) -> D.Query+  dQueryKey :: Proxy a -> hash -> Maybe (RangeOper range) -> D.Query+  dQueryKeyToAttr :: Proxy a -> (hash, range) -> HMap.HashMap T.Text D.AttributeValue instance  (DynamoCollection a 'WithRange 'IsTable, DynamoTable a 'WithRange,-           Code a ~ '[ hash ': range ': rest ], DynamoScalar v1 hash, DynamoScalar v2 range)-    => TableQuery a 'IsTable where+           Code a ~ '[ hash ': range ': rest ], DynamoScalar v1 hash, DynamoScalar v2 range,+           PrimaryKey a 'WithRange ~ (hash, range))+    => CanQuery a 'IsTable hash range where   dQueryKey = defaultQueryKey   qTableName = tableName   qIndexName _ = Nothing+  dQueryKeyToAttr = dKeyToAttr instance  (DynamoCollection a 'WithRange 'IsIndex, DynamoIndex a parent 'WithRange, DynamoTable parent r1,-            Code a ~ '[ hash ': range ': rest ], DynamoScalar v1 hash, DynamoScalar v2 range)-        => TableQuery a 'IsIndex where+            Code a ~ '[ hash ': range ': rest ], DynamoScalar v1 hash, DynamoScalar v2 range,+            PrimaryKey a 'WithRange ~ (hash, range))+        => CanQuery a 'IsIndex hash range where   dQueryKey = defaultQueryKey   qTableName _ = tableName (Proxy :: Proxy parent)   qIndexName = Just . indexName+  dQueryKeyToAttr = dKeyToAttr -class DynamoCollection a r t => TableScan a (r :: RangeType) (t :: TableType) where+-- | Class for tables/indexes that can be scanned+class (DynamoCollection a r t, HasPrimaryKey a r t) => TableScan a (r :: RangeType) (t :: TableType) where   -- | Return table name and index name   qsTableName :: Proxy a -> T.Text   qsIndexName :: Proxy a -> Maybe T.Text   dScan :: Proxy a -> D.Scan-instance DynamoTable a r => TableScan a r 'IsTable where+instance (DynamoCollection a r 'IsTable, DynamoTable a r) => TableScan a r 'IsTable where   qsTableName = tableName   qsIndexName _ = Nothing   dScan = defaultScan instance (DynamoCollection a r 'IsIndex,-          DynamoIndex a parent r, DynamoTable parent r1) => TableScan a r 'IsIndex where+          DynamoIndex a parent r, DynamoTable parent r1, HasPrimaryKey a r 'IsIndex) => TableScan a r 'IsIndex where   qsTableName _ = tableName (Proxy :: Proxy parent)   qsIndexName = Just . indexName   dScan = defaultScan +-- | Type family that returns a primary key of a table/index depending on the 'RangeType' parameter.+type PrimaryKey a r = PrimaryKey' (Code a) r -- | Parameter type for queryKeyRange-type family PrimaryKey (a :: [[*]]) (r :: RangeType) :: * where-    PrimaryKey ('[ key ': range ': rest ] ) 'WithRange = (key, range)-    PrimaryKey ('[ key ': rest ]) 'NoRange = key+type family PrimaryKey' (a :: [[*]]) (r :: RangeType) :: * where+    PrimaryKey' ('[ key ': range ': rest ] ) 'WithRange = (key, range)+    PrimaryKey' ('[ key ': rest ]) 'NoRange = key  -- | Class representing a Global Secondary Index class DynamoCollection a r 'IsIndex => DynamoIndex a parent (r :: RangeType) | a -> parent r where@@ -182,6 +194,7 @@       => IndexCreate a p 'WithRange where   createGlobalIndex = defaultCreateGlobalIndexRange +-- | Class for indexes that contain primary key of the parent table class ContainsTableKey a parent key | a -> parent key where   -- | Extract table primary key from an item, if possible   dTableKey :: a -> key@@ -266,7 +279,7 @@     keyDefs = [D.attributeDefinition hashname (dType (Proxy :: Proxy hash)),                D.attributeDefinition rangename (dType (Proxy :: Proxy range))] -defaultQueryKey :: (TableQuery a t, Code a ~ '[ hash ': range ': rest ], DynamoScalar v1 hash, DynamoScalar v2 range)+defaultQueryKey :: (CanQuery a t hash range, DynamoScalar v1 hash, DynamoScalar v2 range)     => Proxy a -> hash -> Maybe (RangeOper range) -> D.Query defaultQueryKey p key Nothing =   D.query (qTableName p) & D.qKeyConditionExpression .~ Just "#K = :key"
src/Database/DynamoDB/Filter.hs view
@@ -106,7 +106,7 @@ -- | > a /= b === Not (a == b) (/=.) :: (InCollection col tbl 'FullPath, DynamoEncodable typ)         => Column typ ctyp col -> typ -> FilterCondition tbl-(/=.) col val = Not (dcomp "=" col val)+(/=.) col val = Not (col ==. val) infix 4 /=.  
src/Database/DynamoDB/QueryRequest.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP                    #-}+{-# LANGUAGE CPP                 #-} #if __GLASGOW_HASKELL__ >= 800 {-# OPTIONS_GHC -Wno-redundant-constraints #-} #endif@@ -19,10 +19,13 @@   , querySimple   , queryCond   , querySource+  , querySourceChunks+  , queryOverIndex   -- * Scan   , scan   , scanCond   , scanSource+  , scanSourceChunks   -- * Query options   , QueryOpts   , queryOpts@@ -31,48 +34,52 @@   , ScanOpts   , scanOpts   , sFilterCondition, sConsistentRead, sLimit, sParallel, sStartKey+  -- * Helper conduits+  , leftJoin+  , innerJoin ) where  -import           Control.Arrow              (first)-import           Control.Lens               (Lens', view, (%~), (.~), (^.))-import           Control.Lens.TH            (makeLenses)-import           Control.Monad.Catch        (throwM)-import           Data.Bool                  (bool)-import           Data.Coerce                (coerce)-import           Data.Conduit               (Conduit, Source, (=$=))-import qualified Data.Conduit.List          as CL-import           Data.Foldable              (toList)-import           Data.Function              ((&))-import           Data.HashMap.Strict        (HashMap)-import           Data.Monoid                ((<>))+import           Control.Arrow                  (first)+import           Control.Arrow                  (second)+import           Control.Lens                   (Lens', sequenceOf, view, (%~),+                                                 (.~), (^.), _2)+import           Control.Lens.TH                (makeLenses)+import           Control.Monad                  ((>=>))+import           Control.Monad.Catch            (throwM)+import           Data.Bool                      (bool)+import           Data.Coerce                    (coerce)+import           Data.Conduit                   (Conduit, Source, (=$=))+import qualified Data.Conduit.List              as CL+import           Data.Foldable                  (toList)+import           Data.Function                  ((&))+import           Data.HashMap.Strict            (HashMap)+import qualified Data.Map                       as Map+import           Data.Maybe                     (mapMaybe)+import           Data.Monoid                    ((<>)) import           Data.Proxy-import           Data.Sequence              (Seq)-import qualified Data.Sequence              as Seq-import qualified Data.Text                  as T+import           Data.Sequence                  (Seq)+import qualified Data.Sequence                  as Seq+import qualified Data.Text                      as T import           Generics.SOP import           Network.AWS-import qualified Network.AWS.DynamoDB.Query as D-import qualified Network.AWS.DynamoDB.Scan  as D-import qualified Network.AWS.DynamoDB.Types as D-import           Network.AWS.Pager          (AWSPager (..))-import           Numeric.Natural            (Natural)+import qualified Network.AWS.DynamoDB.Query     as D+import qualified Network.AWS.DynamoDB.Scan      as D+import qualified Network.AWS.DynamoDB.Types     as D+import           Network.AWS.Pager              (AWSPager (..))+import           Numeric.Natural                (Natural) +import           Database.DynamoDB.BatchRequest (getItemBatch) import           Database.DynamoDB.Class import           Database.DynamoDB.Filter import           Database.DynamoDB.Internal import           Database.DynamoDB.Types ---- | Helper function to decode data from the conduit.-rsDecode :: (MonadAWS m, Code a ~ '[ hash ': range ': rest], DynamoCollection a r t)-    => (i -> [HashMap T.Text D.AttributeValue]) -> Conduit i m a-rsDecode trans = CL.mapFoldable trans =$= CL.mapM rsDecoder--rsDecoder :: (MonadAWS m, Code a ~ '[ hash ': range ': rest], DynamoCollection a r t)+-- | Decode data, throw exception if decoding fails.+rsDecoder :: (MonadAWS m, DynamoCollection a r t)     => HashMap T.Text D.AttributeValue -> m a rsDecoder item =-  case gsDecode item of+  case dGsDecode item of     Just res -> return res     Nothing -> throwM (DynamoException $ "Error decoding item: " <> T.pack (show item)) @@ -94,10 +101,7 @@ queryOpts key = QueryOpts key Nothing Nothing Eventually Forward Nothing Nothing  -- | Generate a "D.Query" object-queryCmd :: forall a t v1 v2 hash range rest.-    (TableQuery a t, Code a ~ '[ hash ': range ': rest],-     DynamoScalar v1 hash, DynamoScalar v2 range)-  => QueryOpts a hash range -> D.Query+queryCmd :: forall a t hash range. CanQuery a t hash range => QueryOpts a hash range -> D.Query queryCmd q =     dQueryKey (Proxy :: Proxy a) (q ^. qHashKey) (q ^. qRangeCondition)                   & D.qConsistentRead . consistencyL .~ (q ^. qConsistentRead)@@ -115,7 +119,7 @@          . (D.qFilterExpression .~ Just expr)     addStartKey Nothing = id     addStartKey (Just (key, range)) =-        D.qExclusiveStartKey .~ dKeyToAttr (Proxy :: Proxy a) (key, range)+        D.qExclusiveStartKey .~ dQueryKeyToAttr (Proxy :: Proxy a) (key, range)  -- | When https://github.com/brendanhay/amazonka/issues/340 is fixed, remove newtype FixedQuery = FixedQuery D.Query@@ -143,20 +147,40 @@       lastkey = resp ^. D.srsLastEvaluatedKey  +-- | Same as 'querySource', but return data in original chunks.+querySourceChunks :: forall a t m hash range. (CanQuery a t hash range, MonadAWS m)+  => Proxy a -> QueryOpts a hash range -> Source m [a]+querySourceChunks _ q = paginate (FixedQuery (queryCmd q)) =$= CL.mapM (\res -> mapM rsDecoder (res ^. D.qrsItems))++ -- | Generic query function. You can query table or indexes that have -- a range key defined. The filter condition cannot access the hash and range keys.-querySource :: forall a t m v1 v2 hash range rest.-    (TableQuery a t, MonadAWS m, Code a ~ '[ hash ': range ': rest],-     DynamoScalar v1 hash, DynamoScalar v2 range)+querySource :: forall a t m hash range. (CanQuery a t hash range, MonadAWS m)   => Proxy a -> QueryOpts a hash range -> Source m a-querySource _ q = paginate (FixedQuery (queryCmd q)) =$= rsDecode (view D.qrsItems)+querySource p q = querySourceChunks p q =$= CL.concat +-- | Query an index, fetch primary key from the result and immediately read+-- full items from the main table.+queryOverIndex :: forall a t m v1 v2 hash r2 range rest parent.+    (CanQuery a t hash range, MonadAWS m,+     Code a ~ '[ hash ': range ': rest],+     DynamoIndex a parent 'WithRange, ContainsTableKey a parent (PrimaryKey parent r2),+     DynamoTable parent r2,+     DynamoScalar v1 hash, DynamoScalar v2 range)+  => Proxy a -> QueryOpts a hash range -> Source m parent+queryOverIndex p q =+   querySourceChunks p q =$= CL.mapFoldableM batchParent+  where+    batchParent vals = do+      let keys = map dTableKey vals+      -- TODO: it would be nice if we could paginate requests from getItemBatch downstraem+      getItemBatch (q ^. qConsistentRead) keys+ -- | Perform a simple, eventually consistent, query. -- -- Simple to use function to query limited amount of data from database.-querySimple :: forall a t v1 v2 m hash range rest.-  (TableQuery a t, MonadAWS m, Code a ~ '[ hash ': range ': rest],-   DynamoScalar v1 hash, DynamoScalar v2 range)+querySimple :: forall a t m hash range.+  (CanQuery a t hash range, MonadAWS m)   => Proxy a -- ^ Proxy type of a table to query   -> hash        -- ^ Hash key   -> Maybe (RangeOper range) -- ^ Range condition@@ -169,9 +193,8 @@   fst <$> query p opts limit  -- | Query with condition-queryCond :: forall a t v1 v2 m hash range rest.-  (TableQuery a t, MonadAWS m, Code a ~ '[ hash ': range ': rest],-   DynamoScalar v1 hash, DynamoScalar v2 range)+queryCond :: forall a t m hash range.+  (CanQuery a t hash range, MonadAWS m)   => Proxy a   -> hash        -- ^ Hash key   -> Maybe (RangeOper range) -- ^ Range condition@@ -188,13 +211,12 @@ -- | Fetch exactly the required count of items even when -- it means more calls to dynamodb. Return last evaluted key if end of data -- was not reached. Use 'qStartKey' to continue reading the query.-query :: forall a t v1 v2 m range hash rest.-  (TableQuery a t, DynamoCollection a 'WithRange t, MonadAWS m, Code a ~ '[ hash ': range ': rest],-   DynamoScalar v1 hash, DynamoScalar v2 range)+query :: forall a t m range hash.+  (CanQuery a t hash range, MonadAWS m)   => Proxy a   -> QueryOpts a hash range   -> Int -- ^ Maximum number of items to fetch-  -> m ([a], Maybe (PrimaryKey (Code a) 'WithRange))+  -> m ([a], Maybe (PrimaryKey a 'WithRange)) query _ opts limit = do     -- Add qLimit to the opts if not already there - and if there is no condition     let cmd = queryCmd (opts & addQLimit)@@ -205,14 +227,14 @@       | otherwise = id  -- | Generic query interface for scanning/querying-boundedFetch :: forall a r t m range hash cmd rest.-  (MonadAWS m, HasPrimaryKey a r t, Code a ~ '[ hash ': range ': rest], AWSRequest cmd)+boundedFetch :: forall a r t m cmd.+  (MonadAWS m, HasPrimaryKey a r t, AWSRequest cmd)   => Lens' cmd (HashMap T.Text D.AttributeValue)   -> (Rs cmd -> [HashMap T.Text D.AttributeValue])   -> (Rs cmd -> HashMap T.Text D.AttributeValue)   -> cmd   -> Int -- ^ Maximum number of items to fetch-  -> m ([a], Maybe (PrimaryKey (Code a) r))+  -> m ([a], Maybe (PrimaryKey a r)) boundedFetch startLens rsResult rsLast startcmd limit = do       (result, nextcmd) <- unfoldLimit fetch startcmd limit       if | length result > limit ->@@ -231,6 +253,7 @@             newquery = bool (Just (cmd & startLens .~ lastkey)) Nothing (null lastkey)         return (items, newquery) +-- | Run command as long as Maybe cmd is Just or the resulting sequence is smaller than limit unfoldLimit :: Monad m => (cmd -> m (Seq a, Maybe cmd)) -> cmd -> Int -> m (Seq a, Maybe cmd) unfoldLimit code = go   where@@ -248,25 +271,28 @@   , _sConsistentRead :: Consistency   , _sLimit :: Maybe Natural   , _sParallel :: Maybe (Natural, Natural) -- ^ (Segment number, TotalSegments)-  , _sStartKey :: Maybe (PrimaryKey (Code a) r)+  , _sStartKey :: Maybe (PrimaryKey a r) } makeLenses ''ScanOpts scanOpts :: ScanOpts a r scanOpts = ScanOpts Nothing Eventually Nothing Nothing Nothing +-- | Conduit source for running scan; the same as 'scanSource', but return results in chunks as they come.+scanSourceChunks :: (MonadAWS m, TableScan a r t) => Proxy a -> ScanOpts a r -> Source m [a]+scanSourceChunks _ q = paginate (FixedScan (scanCmd q)) =$= CL.mapM (\res -> mapM rsDecoder (res ^. D.srsItems))+ -- | Conduit source for running a scan.-scanSource :: (MonadAWS m, TableScan a r t, HasPrimaryKey a r t, Code a ~ '[hash ': range ': xss])-  => Proxy a -> ScanOpts a r -> Source m a-scanSource _ q = paginate (FixedScan $ scanCmd q) =$= rsDecode (view D.srsItems)+scanSource :: (MonadAWS m, TableScan a r t) => Proxy a -> ScanOpts a r -> Source m a+scanSource p q = scanSourceChunks p q =$= CL.concat  -- | Function to call bounded scans. Tries to return exactly requested number of items. -- -- Use 'sStartKey' to continue the scan.-scan :: (MonadAWS m, Code a ~ '[ hash ': range ': rest], TableScan a r t, HasPrimaryKey a r t)+scan :: (MonadAWS m, TableScan a r t)   => Proxy a   -> ScanOpts a r  -- ^ Scan settings   -> Int  -- ^ Required result count-  -> m ([a], Maybe (PrimaryKey (Code a) r)) -- ^ list of results, lastEvalutedKey or Nothing if end of data reached+  -> m ([a], Maybe (PrimaryKey a r)) -- ^ list of results, lastEvalutedKey or Nothing if end of data reached scan _ opts limit = do     let cmd = scanCmd (opts & addSLimit)     boundedFetch D.sExclusiveStartKey (view D.srsItems) (view D.srsLastEvaluatedKey) cmd limit@@ -276,10 +302,8 @@       | Nothing <- opts ^. sLimit, Nothing <- opts ^. sFilterCondition = sLimit .~ Just (fromIntegral limit)       | otherwise = id --- | Generate a "D.Query" object-scanCmd :: forall a r t hash range xss.-    (TableScan a r t, HasPrimaryKey a r t, Code a ~ '[hash ': range ': xss])-  => ScanOpts a r -> D.Scan+-- | Generate a "D.Query" object.+scanCmd :: forall a r t. TableScan a r t => ScanOpts a r -> D.Scan scanCmd q =     dScan (Proxy :: Proxy a)         & D.sConsistentRead . consistencyL .~ (q ^. sConsistentRead)@@ -308,10 +332,35 @@ -- | Scan table using a given filter condition. -- -- > scanCond (colAddress <!:> "Home" <.> colCity ==. "London") 10-scanCond :: forall a m hash range rest r t.-    (MonadAWS m, HasPrimaryKey a r t, Code a ~ '[ hash ': range ': rest], TableScan a r t)-    => Proxy a -> FilterCondition a -> Int -> m [a]+scanCond :: forall a m r t. (MonadAWS m, TableScan a r t) => Proxy a -> FilterCondition a -> Int -> m [a] scanCond _ cond limit = do   let opts = scanOpts & sFilterCondition .~ Just cond       cmd = scanCmd opts   fst <$> boundedFetch D.sExclusiveStartKey (view D.srsItems) (view D.srsLastEvaluatedKey) cmd limit++-- | Conduit to do a left join on the items being sent; supposed to be used with querySourceChunks.+--+-- The 'foreign key' must have an 'Ord' to facilitate faster searching.+leftJoin :: forall a b m r.+    (MonadAWS m, DynamoTable a r, Ord (PrimaryKey a r), ContainsTableKey a a (PrimaryKey a r))+    => Consistency+    -> Proxy a -- ^ Proxy type for the right table+    -> (b -> Maybe (PrimaryKey a r))+    -> Conduit [b] m [(b, Maybe a)]+leftJoin consistency p getkey = CL.mapM doJoin+  where+    doJoin input = do+      let keys = filter (dKeyIsDefined p) $ mapMaybe getkey input+      rightTbl <- getItemBatch consistency keys+      let resultMap = Map.fromList $ map (\res -> (dTableKey res,res)) rightTbl+      return $ map (second (id >=> (`Map.lookup` resultMap))) $ zip input $ map getkey input++-- | The same as 'leftJoin', but discard items that do not exist in the right table.+innerJoin :: forall a b m r.+    (MonadAWS m, DynamoTable a r, Ord (PrimaryKey a r), ContainsTableKey a a (PrimaryKey a r))+    => Consistency+    -> Proxy a -- ^ Proxy type for the right table+    -> (b -> Maybe (PrimaryKey a r))+    -> Conduit [b] m [(b, a)]+innerJoin consistency p getkey =+    leftJoin consistency p getkey =$= CL.map (mapMaybe (sequenceOf _2))
src/Database/DynamoDB/TH.hs view
@@ -28,8 +28,8 @@   , RangeType(..) ) where -import           Control.Lens                    (ix, over, (.~), (^.), _1, view)-import           Control.Monad                   (forM_, when)+import           Control.Lens                    (ix, over, (.~), (^.), _1, view, (^..))+import           Control.Monad                   (forM_, unless, when) import           Control.Monad.Trans.Class       (lift) import           Control.Monad.Trans.Writer.Lazy (WriterT, execWriterT, tell) import           Data.Bool                       (bool)@@ -51,6 +51,7 @@ import           Database.DynamoDB.Internal import           Database.DynamoDB.THLens import           Database.DynamoDB.THContains+import           Database.DynamoDB.THConvert  -- | Configuration of TH macro for creating table instances data TableConfig = TableConfig {@@ -91,8 +92,8 @@ -- -- Example of what gets created: ----- > data Test { first :: Text, second :: Text, third :: Int }--- > data TestIndex { third :: Int, second :: T.Text}+-- > data Test { _first :: Text, _second :: Text, _third :: Int }+-- > data TestIndex { u_third :: Int, i_second :: T.Text} -- > -- > mkTableDefs (tableConfig (''Test, WithRange) [(''TestIndex, NoRange)] []) -- >@@ -115,6 +116,11 @@ -- > instance InCollection P_Second TestIndex 'FullPath -- For every non-primary attribute -- > first' :: Column Text TypColumn P_First -- > first' = Column+-- > -- Polymorphic lenses+-- > class Test_lens_first a b | a -> b where+-- >    first :: Functor f => (a -> f b) -> a -> f b+-- > instance Test_lens_first TestIndex Text where+-- >    first = ... mkTableDefs ::     String -- ^ Name of the migration function   -> TableConfig@@ -163,8 +169,11 @@         createPolyLenses translateField table (map (view _1) allindexes)     -- Create ContainsTableKey instances to easily extract     let pkey = map fst $ take (pkeySize tblrange) tblFieldNames-    forM_ (table : map (\(a,_,_) -> a) allindexes) $+    forM_ (table : (allindexes ^.. traverse . _1)) $         createContainsTableKey translateField table pkey+    -- Create toTable instances/classes+    unless (null allindexes) $+        createTableConversions translateField table (allindexes ^.. traverse . _1)  pkeySize :: RangeType -> Int pkeySize WithRange = 2@@ -320,12 +329,14 @@ -- -- * Table by default equals name of the type. -- * Attribute names in an index table must be the same as attribute names in the main table---   (translateField tableFieldName == translateField indexFieldName)+--   (translateField tableFieldName == translateField indexFieldName). -- * Attribute name is a field name from a first underscore ('tId'). This should make it compatibile with lens. -- * Column name is an attribute name with appended tick: tId'--- * Predefined proxies starting with "t" for tables and "i" for indexes (e.g. 'tTest', 'iTestIndex')--- * Polymorphic lens to access fields in both tables and indexes--- * Auxiliary datatype for column is P_ followed by capitalized attribute name ('P_TId')+-- * Predefined proxies starting with "t" for tables and "i" for indexes (e.g. 'tTest', 'iTestIndex').+-- * Polymorphic lens to access fields in both tables and indexes.+-- * For indexes with the same dataset as the base table, the conversion function (e.g. 'toTest')+--   gets created for easy conversion between index and base type.+-- * Auxiliary datatype for column is P_ followed by capitalized attribute name ('P_TId'). -- -- @ -- data Test = Test {
src/Database/DynamoDB/THContains.hs view
@@ -2,13 +2,13 @@ module Database.DynamoDB.THContains where  import           Control.Monad.Trans.Class       (lift)-import Data.List (find)-import Language.Haskell.TH-import Data.Function ((&)) import           Control.Monad.Trans.Writer.Lazy (WriterT, tell)+import           Data.Function                   ((&))+import           Data.List                       (find)+import           Language.Haskell.TH -import Database.DynamoDB.THLens (getFieldNames, whenJust)-import Database.DynamoDB.Class (ContainsTableKey(..))+import           Database.DynamoDB.Class         (ContainsTableKey (..))+import           Database.DynamoDB.THLens        (getFieldNames, whenJust)  -- | Create ContainsTableKey instance createContainsTableKey :: (String -> String) -> Name -> [String] -> Name -> WriterT [Dec] Q ()
+ src/Database/DynamoDB/THConvert.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE CPP             #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE LambdaCase #-}++-- | Module generating 'toTable' class/instance for converting from index to main table type+module Database.DynamoDB.THConvert (+  createTableConversions+) where++import           Control.Monad                   (replicateM, when)+import           Control.Monad.Trans.Class       (lift)+import           Control.Monad.Trans.Writer.Lazy (WriterT, pass)+import           Data.List                       (elemIndex, sort)+import           Data.Monoid                     ((<>))+import           Language.Haskell.TH++import           Database.DynamoDB.THLens        (getFieldNames, say)++getConstructor :: Name -> Q Name+getConstructor tbl = do+    info <- reify tbl+    case info of+#if __GLASGOW_HASKELL__ >= 800+        (TyConI (DataD _ _ _ _ [RecC name _] _)) -> return name+#else+        (TyConI (DataD _ _ _ [RecC name _] _)) -> return name+#endif+        _ -> fail "not a record declaration with 1 constructor"++-- | Create function that converts complete structure from index back to table.+--+-- DynamoDB limits total number of attribute projections to 20, so this may+-- not be as useful as it appears.+createTableConversions :: (String -> String) -> Name -> [Name] -> WriterT [Dec] Q ()+createTableConversions translate table idxes = do+    tblFields <- getFieldNames table translate+    tblConstr <- lift $ getConstructor table+    clsname <- lift $ newName $ "IndexToTable_" <> nameBase tblConstr+    a <- lift $ newName "a"+    let clsdef = ClassD [] clsname [PlainTV a] [] [SigD funcname (AppT (AppT ArrowT (VarT a)) (ConT table))]+    let instth = mapM_ (mkInstance tblFields tblConstr clsname) idxes+    -- Create a typeclass only if something got created+    pass (instth >> return ((), \case {[] -> []; lst -> clsdef:lst}))+  where+    funcname = mkName ("to" <> nameBase table)++    mkInstance tblFields tblConstr clsname idxname = do+        let tblNames = map fst tblFields+        idxFields <- getFieldNames idxname translate+        let idxNames = map fst idxFields+        idxConstr <- lift $ getConstructor idxname++        when (sort tblNames == sort idxNames) $+          case mapM (`elemIndex` idxNames) tblNames of+              Nothing -> return ()+              Just varidxmap -> do+                  varnames <- lift $ replicateM (length idxNames) (newName "a")+                  let ivars = map varP varnames+                  let toJust = zipWith makeJust (map snd tblFields) (map (snd . (idxFields !!)) varidxmap)+                      olist = zipWith ($) toJust $ map (varnames !!) varidxmap+                      ovars = foldl appE (conE tblConstr) olist+                  let func = funD funcname [clause [conP idxConstr ivars] (normalB ovars) []]+                  lift (instanceD (pure []) (appT (conT clsname) (conT idxname))+                        [func]) >>= say++    makeJust (AppT (ConT mbtype) dsttype) srctype+        | mbtype == ''Maybe && dsttype == srctype = appE (conE 'Just) . varE+    makeJust _ _ = varE
src/Database/DynamoDB/Types.hs view
@@ -59,6 +59,7 @@                                               attributeValue) import qualified Network.AWS.DynamoDB.Types  as D import           Text.Read                   (readMaybe)+import Data.Int (Int16, Int32, Int64)   -- | Exceptions thrown by some dynamodb-simple actions.@@ -110,7 +111,7 @@ -- > instance DynamoScalar Network.AWS.DynamoDB.Types.S T.Text where -- >    scalarEncode = ScS -- >    scalarDecode (ScS txt) = Just txt-class (ScalarAuto v, DynamoEncodable a) => DynamoScalar (v :: D.ScalarAttributeType) a | a -> v where+class ScalarAuto v => DynamoScalar (v :: D.ScalarAttributeType) a | a -> v where   -- | Scalars must have total encoding function   scalarEncode :: a -> ScalarValue v   default scalarEncode :: (Show a, Read a) => a -> ScalarValue 'D.S@@ -130,13 +131,22 @@ instance DynamoScalar 'D.N Int where   scalarEncode = ScN . fromIntegral   scalarDecode (ScN num) = toBoundedInteger num+instance DynamoScalar 'D.N Int16 where+  scalarEncode = ScN . fromIntegral+  scalarDecode (ScN num) = toBoundedInteger num+instance DynamoScalar 'D.N Int32 where+  scalarEncode = ScN . fromIntegral+  scalarDecode (ScN num) = toBoundedInteger num+instance DynamoScalar 'D.N Int64 where+  scalarEncode = ScN . fromIntegral+  scalarDecode (ScN num) = toBoundedInteger num  instance DynamoScalar 'D.N Word where   scalarEncode = ScN . fromIntegral   scalarDecode (ScN num) = toBoundedInteger num  -- | Helper for tagged values-instance DynamoScalar v a => DynamoScalar v (Tagged x a) where+instance {-# OVERLAPPABLE #-} DynamoScalar v a => DynamoScalar v (Tagged x a) where   scalarEncode = scalarEncode . unTagged   scalarDecode a = Tagged <$> scalarDecode a @@ -174,6 +184,7 @@   dDecode Nothing = Nothing   -- | Aid for searching for empty list and hashmap; these can be represented   -- both by empty list and by missing value, if this returns true, enhance search.+  -- Also used by joins to weed out empty foreign keys   dIsMissing :: a -> Bool   dIsMissing _ = False @@ -193,6 +204,18 @@   dEncode = Just . dScalarEncode   dDecode (Just attr) = attr ^. D.avN >>= AE.decodeStrict . encodeUtf8   dDecode Nothing = Nothing -- Fail on missing attr+instance DynamoEncodable Int16 where+  dEncode = Just . dScalarEncode+  dDecode (Just attr) = attr ^. D.avN >>= AE.decodeStrict . encodeUtf8+  dDecode Nothing = Nothing -- Fail on missing attr+instance DynamoEncodable Int32 where+  dEncode = Just . dScalarEncode+  dDecode (Just attr) = attr ^. D.avN >>= AE.decodeStrict . encodeUtf8+  dDecode Nothing = Nothing -- Fail on missing attr+instance DynamoEncodable Int64 where+  dEncode = Just . dScalarEncode+  dDecode (Just attr) = attr ^. D.avN >>= AE.decodeStrict . encodeUtf8+  dDecode Nothing = Nothing -- Fail on missing attr instance DynamoEncodable Double where   dEncode num = Just $ attributeValue & D.avN .~ (Just $ toShortest num)   dDecode (Just attr) = attr ^. D.avN >>= AE.decodeStrict . encodeUtf8@@ -208,11 +231,15 @@     | Just True <- attr ^. D.avNULL = Just ""     | otherwise = attr ^. D.avS   dDecode Nothing = Just ""+  dIsMissing "" = True+  dIsMissing _ = False instance DynamoEncodable BS.ByteString where   dEncode "" = Nothing   dEncode bs = Just (dScalarEncode bs)   dDecode (Just attr) = attr ^. D.avB   dDecode Nothing = Just ""+  dIsMissing "" = True+  dIsMissing _ = False  -- | 'Maybe' ('Maybe' a) will not work well; it will 'join' the value in the database. instance DynamoEncodable a => DynamoEncodable (Maybe a) where@@ -220,6 +247,8 @@   dEncode (Just key) = dEncode key   dDecode Nothing = Just Nothing   dDecode (Just attr) = Just <$> dDecode (Just attr)+  dIsMissing Nothing = True+  dIsMissing _ = False instance (Ord a, DynamoScalar v a) => DynamoEncodable (Set.Set a) where   dEncode (Set.null -> True) = Nothing   dEncode dta = Just $ dSetEncode dta@@ -241,7 +270,7 @@   dDecode Nothing = Just mempty   dIsMissing = null -instance DynamoEncodable a => DynamoEncodable (Tagged v a) where+instance {-# OVERLAPPABLE #-} DynamoEncodable a => DynamoEncodable (Tagged v a) where   dEncode = dEncode . unTagged   dDecode a = Tagged <$> dDecode a   dIsMissing = dIsMissing . unTagged
test/BaseSpec.hs view
@@ -44,6 +44,12 @@ } deriving (Show, Eq, Ord) mkTableDefs "migrateTest" (tableConfig (''Test, WithRange) [] []) +data TestSecond = TestSecond {+    tHashKey :: T.Text+  , tInt :: Int+} deriving (Show, Eq)+mkTableDefs "migrateTest2" (tableConfig (''TestSecond, NoRange) [] [])+ withDb :: Example (IO b) => String -> AWS b -> SpecWith (Arg (IO b)) withDb msg code = it msg runcode   where@@ -58,6 +64,7 @@       runResourceT $ runAWS newenv $ do           deleteTable (Proxy :: Proxy Test) `catchAny` (\_ -> return ())           migrateTest mempty Nothing+          migrateTest2 mempty Nothing           code `finally` deleteTable (Proxy :: Proxy Test)  spec :: Spec@@ -68,8 +75,8 @@             testitem2 = Test "2" 3 "text" False 4.15 3 (Just "text")         putItem testitem1         putItem testitem2-        it1 <- getItem Strongly (tTest, ("1", 2))-        it2 <- getItem Strongly (tTest, ("2", 3))+        it1 <- getItem Strongly tTest ("1", 2)+        it2 <- getItem Strongly tTest ("2", 3)         liftIO $ Just testitem1 `shouldBe` it1         liftIO $ Just testitem2 `shouldBe` it2     withDb "getItemBatch/putItemBatch work" $ do@@ -77,7 +84,7 @@             newItems = map template [1..300]         putItemBatch newItems         ---        let keys = map (snd . tableKey) newItems+        let keys = map tableKey newItems         items <- getItemBatch Strongly keys         liftIO $ sort items `shouldBe` sort newItems     withDb "insertItem doesn't overwrite items" $ do@@ -154,9 +161,9 @@     withDb "updateItemByKey works" $ do         let testitem1 = Test "1" 2 "text" False 3.14 2 (Just "something")         putItem testitem1-        new1 <- updateItemByKey (tableKey testitem1)+        new1 <- updateItemByKey tTest (tableKey testitem1)                                 ((iInt' +=. 5) <> (iText' =. "updated") <> (iMText' =. Nothing))-        Just new2 <- getItem Strongly (tableKey testitem1)+        Just new2 <- getItem Strongly tTest (tableKey testitem1)         liftIO $ do             new1 `shouldBe` new2             iInt new1 `shouldBe` 7@@ -166,8 +173,8 @@     withDb "update fails on non-existing item" $ do         let testitem1 = Test "1" 2 "text" False 3.14 2 (Just "something")         putItem testitem1-        updateItemByKey_ (tTest, ("1", 2)) (iBool' =. True)-        (res :: Either SomeException ()) <- try $ updateItemByKey_ (tTest, ("2", 3)) (iBool' =. True)+        updateItemByKey_ tTest ("1", 2) (iBool' =. True)+        (res :: Either SomeException ()) <- try $ updateItemByKey_ tTest ("2", 3) (iBool' =. True)         liftIO $ res `shouldSatisfy` isLeft      withDb "scan continuation works" $ do@@ -198,12 +205,28 @@         putItem testitem1         putItem testitem2         (items, _) <- scan tTest scanOpts 10-        deleteItemByKey (tableKey testitem1)-        deleteItemByKey (tableKey testitem2)+        deleteItemByKey tTest (tableKey testitem1)+        deleteItemByKey tTest (tableKey testitem2)         (items2, _) <- scan tTest scanOpts 10         liftIO $ do           length items `shouldBe` 2           length items2 `shouldBe` 0++    withDb "test left join" $ do+        let testitem1 = Test "1" 2 "" False 3.14 2 Nothing+        let testitem2 = Test "1" 3 "aaa" False 3.14 2 (Just "aaa")+        let testsecond = TestSecond "aaa" 3+        putItem testitem1+        putItem testitem2+        putItem testsecond+        res <- runConduit $ querySourceChunks tTest (queryOpts "1")+                              =$= leftJoin Strongly tTestSecond (Just . iText)+                              =$= CL.concat =$= CL.consume+        liftIO $ res `shouldBe` [(testitem1, Nothing), (testitem2, Just testsecond)]+        res2 <- runConduit $ querySourceChunks tTest (queryOpts "1")+                              =$= leftJoin Strongly tTestSecond iMText+                              =$= CL.concat =$= CL.consume+        liftIO $ res2 `shouldBe` [(testitem1, Nothing), (testitem2, Just testsecond)]   main :: IO ()
test/NestedSpec.hs view
@@ -14,7 +14,7 @@  import           Control.Exception.Safe   (catchAny, finally) import           Control.Monad.IO.Class   (liftIO)-import           Control.Lens             (set, makeLenses, (^.), (^?), ix, (^..))+import           Control.Lens             (makeLenses, (^.), (^?), ix, (^..)) import           Data.Function            ((&)) import           Data.Hashable import           Data.HashMap.Strict      (HashMap)@@ -83,8 +83,8 @@             testitem2 = Test "hash" 2 inner1 Nothing Set.empty [] HMap.empty         putItem testitem1         putItem testitem2-        Just ritem1 <- getItem Strongly (tTest, ("hash", 1))-        Just ritem2 <- getItem Strongly (tTest, ("hash", 2))+        Just ritem1 <- getItem Strongly tTest ("hash", 1)+        Just ritem2 <- getItem Strongly tTest ("hash", 2)         liftIO $ testitem1 `shouldBe` ritem1         liftIO $ testitem2 `shouldBe` ritem2     withDb "Scan conditions" $ do@@ -124,23 +124,23 @@         putItem testitem1         putItem testitem2         ---        newitem1 <- updateItemByKey (tableKey testitem1) (iInner' <.> nFirst' =. "updated")+        newitem1 <- updateItemByKey tTest (tableKey testitem1) (iInner' <.> nFirst' =. "updated")         liftIO $ newitem1 ^. iInner . nFirst `shouldBe` "updated"         ---        newitem2 <- updateItemByKey (tableKey testitem1) (add iSet' (Set.singleton (Tagged "test2")))+        newitem2 <- updateItemByKey tTest (tableKey testitem1) (add iSet' (Set.singleton (Tagged "test2")))         liftIO $ newitem2 ^. iSet `shouldBe` Set.fromList [Tagged "test", Tagged "test2"]         ---        newitem3 <- updateItemByKey (tableKey testitem1) (prepend iList' [inner2, inner1])+        newitem3 <- updateItemByKey tTest (tableKey testitem1) (prepend iList' [inner2, inner1])         liftIO $ newitem3 ^. iList `shouldBe` [inner2, inner1, inner1]         ---        newitem4 <- updateItemByKey (tableKey testitem1) (delListItem iList' 1)+        newitem4 <- updateItemByKey tTest (tableKey testitem1) (delListItem iList' 1)         liftIO $ newitem4 ^. iList `shouldBe` [inner2, inner1]         ---        newitem5 <- updateItemByKey (tableKey testitem2) (iMap' <!:> Tagged "test" =. inner1)+        newitem5 <- updateItemByKey tTest (tableKey testitem2) (iMap' <!:> Tagged "test" =. inner1)         liftIO $ newitem5 ^.. iMap . traverse `shouldBe` [inner1]         liftIO $ newitem5 ^? iMap . ix (Tagged "test")  `shouldBe` Just inner1         ---        newitem6 <- updateItemByKey (tableKey testitem2) (delHashKey iMap' (Tagged "test"))+        newitem6 <- updateItemByKey tTest (tableKey testitem2) (delHashKey iMap' (Tagged "test"))         liftIO $ newitem6 ^. iMap `shouldBe` HMap.empty