{-# LANGUAGE TypeFamilies, FlexibleContexts, ScopedTypeVariables #-} {-# LANGUAGE PolyKinds #-} -- TODO: dInsert and iDelete should not just invoke "error", but a proper fix is most likely to define a relational transaction that supports this correctly {-| Module : Database.HaskRel.Relational.Assignment Description : Relational assignment Copyright : © Thor Michael Støre, 2015 License : GPL v2 without "any later version" clause Maintainer : thormichael át gmail døt com Stability : experimental Relational assignment and specalizations thereof. As with "Database.HaskRel.Relational.Algebra" this does not support relational expressions building on relvars, but defers that to "Database.HaskRel.Relational.Expression". -} module Database.HaskRel.Relational.Assignment ( -- * The primitive assignment function assign, -- * Specialized assignment functions insert, dInsert, update, updateAll, delete, iDelete, deleteP, -- * Further specialized and simplified forms of update updateA, updateAllA ) where import Control.Monad ( unless ) import Data.HList.CommonMain import Data.Set ( Set, filter, difference, fromList, size ) import qualified Data.Set ( map, foldr ) import Data.Typeable ( Typeable ) import System.Directory ( renameFile ) import Database.HaskRel.Relational.Definition ( Relation, RTuple, bodyAsList, relRearrange' ) import Database.HaskRel.HFWTabulation ( HPresentRecAttr, showHRecSetTab ) import Database.HaskRel.Relational.Algebra ( intersect, minus, minus_ ) import Database.HaskRel.Relational.Variable rewriteRelvar :: (Show (HList (RecordValuesR r)), RecordValues r) => Relvar a -> Relation r -> IO () rewriteRelvar rv updated = do writeRelvarBody ( relvarPath rv ++ ".new" ) ( bodyAsList updated ) renameFile ( relvarPath rv ++ ".new" ) ( relvarPath rv ) -- == Relation variable update operations == -- -- | Writes a relation value to a relvar file, replacing the existing value. assign :: (Ord (HList a), Show (HList (RecordValuesR a)), RecordValues a, HRearrange3 (LabelsOf a) r a, HLabelSet (LabelsOf a), SameLength' r a, SameLength' r (LabelsOf a), SameLength' a r, SameLength' (LabelsOf a) r) => Relvar a -> Relation r -> IO () assign rv r = do rewriteRelvar rv ( relRearrange' r $ relvarType rv ) putStrLn $ "Value assigned to " ++ relvarPath rv appendRelvar :: (Show (t a), Foldable t) => Relvar t1 -> t a -> Bool -> IO () appendRelvar rv hll empty = let prefix = if empty then "" else "," in unless (null hll) $ appendFile (relvarPath rv) $ prefix ++ init ( tail $ show hll ) -- == Inserts {-| Inserts a relation into a relvar. This differs from SQL's INSERT; the relvar is updated to the union of the relvar and the relation value given as arguments. See `Database.HaskRel.Relational.Expression.insert`. -} insert :: (Ord (HList a), Read (HList (RecordValuesR a)), Show (HList (RecordValuesR a)), RecordValues a, HRearrange3 (LabelsOf a) r a, HLabelSet (LabelsOf a), HMapAux HList TaggedFn (RecordValuesR a) a, SameLength' r a, SameLength' r (LabelsOf a), SameLength' a r, SameLength' (LabelsOf a) r) => Relvar a -> Relation r -> IO () insert rv r = do rv' <- readRelvar rv let diff = ( r `minus_` rv' ) in do appendRelvar rv ( bodyAsList diff ) ( null rv' ) putStrLn $ "Inserted " ++ show ( size diff ) ++ " of " ++ show ( size r ) ++ " tuples into " ++ relvarPath rv -- Note: "minus_" is used in place of "minus" to rearrange the relation to the relvar, and not vice-versa, as it must be. {-| Disjoint insert. Closer to SQL INSERT, except that this will never insert a duplicate tuple. See `Database.HaskRel.Relational.Expression.dInsert`. -} dInsert :: (Ord (HList t), Read (HList (RecordValuesR t)), Show (HList (RecordValuesR r)), Typeable t, RecordValues r, RecordValues t, HRearrange3 (LabelsOf t) r t, HLabelSet (LabelsOf t), HMapAux HList TaggedFn (RecordValuesR t) t, HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR t) [[String]], SameLength' r t, SameLength' r (LabelsOf t), SameLength' t r, SameLength' (LabelsOf t) r) => Relvar t -> Relation r -> IO () dInsert rv r = do rv' <- readRelvar rv let inter = ( rv' `intersect` r ) in if not ( null inter ) then error $ "Unique constraint violation, tuples already present in " ++ relvarPath rv ++ ":\n" ++ showHRecSetTab inter else do appendRelvar rv ( bodyAsList r ) ( null rv' ) putStrLn $ "Inserted " ++ show ( size r ) ++ " tuples into " ++ relvarPath rv -- == Updates -- Warning: Doesn't infer the way I'd like it to. funSelfUpdate :: (HRearrange3 (LabelsOf r') (HAppendListR r r'2) r', HLabelSet (LabelsOf r'), HLabelSet (LabelsOf (HAppendListR r r'2)), HDeleteLabels (LabelsOf r) r' r'2, HAppendList r r'2, SameLength' r' (HAppendListR r r'2), SameLength' (LabelsOf r') (HAppendListR r r'2), SameLength' (HAppendListR r r'2) r', SameLength' (HAppendListR r r'2) (LabelsOf r'), HAllTaggedLV (HAppendListR r r'2)) => (Record r' -> Record r) -> Record r' -> Record r' funSelfUpdate f t = hRearrange ( labelsOf t ) ( f t .<++. t ) update' :: (Num t, Num t1, Ord (HList r'), HRearrange3 (LabelsOf r') (HAppendListR r r'2) r', HLabelSet (LabelsOf r'), HLabelSet (LabelsOf (HAppendListR r r'2)), HDeleteLabels (LabelsOf r) r' r'2, HAppendList r r'2, SameLength' r' (HAppendListR r r'2), SameLength' (LabelsOf r') (HAppendListR r r'2), SameLength' (HAppendListR r r'2) r', SameLength' (HAppendListR r r'2) (LabelsOf r'), HAllTaggedLV (HAppendListR r r'2)) => Set (Record r') -> (Record r' -> Bool) -> (Record r' -> Record r) -> (t, t1, Set (Record r')) update' r p f = update'' r p (funSelfUpdate f) updateA' :: (Num t, Num t1, Ord (record r), HUpdateAtLabel record l v r r, SameLength' r r) => Set (record r) -> (record r -> Bool) -> (record r -> Tagged l v) -> (t, t1, Set (record r)) updateA' r p f = update'' r p (\t -> f t .<. t) update'' :: (Num t, Num t1, Ord a) => Set a -> (a -> Bool) -> (a -> a) -> (t, t1, Set a) update'' r p f = let (a,b,c) = Data.Set.foldr (\t (a',b',c') -> if p t then ( a' + 1, b' + 1, f t : c' ) else ( a', b' + 1, t : c' ) ) (0,0,[]) r in (a, b, fromList c) updateAll' :: (Num t, Ord (HList r'), HRearrange3 (LabelsOf r') (HAppendListR r r'2) r', HLabelSet (LabelsOf r'), HLabelSet (LabelsOf (HAppendListR r r'2)), HDeleteLabels (LabelsOf r) r' r'2, HAppendList r r'2, SameLength' r' (HAppendListR r r'2), SameLength' (LabelsOf r') (HAppendListR r r'2), SameLength' (HAppendListR r r'2) r', SameLength' (HAppendListR r r'2) (LabelsOf r'), HAllTaggedLV (HAppendListR r r'2)) => Set (Record r') -> (Record r' -> Record r) -> (t, Set (Record r')) updateAll' r f = updateAll'' r (funSelfUpdate f) updateAllA' :: (Num t, Ord (record r), HUpdateAtLabel record l v r r, SameLength' r r) => Set (record r) -> (record r -> Tagged l v) -> (t, Set (record r)) updateAllA' r f = updateAll'' r (\t -> f t .<. t) updateAll'' :: (Num t, Ord a1) => Set a -> (a -> a1) -> (t, Set a1) updateAll'' r f = let (a,b) = Data.Set.foldr (\t (a',b') -> ( a' + 1, f t : b' ) ) (0,[]) r in (a, fromList b) doUpdate :: (Show a, Show a1, Show (HList (RecordValuesR r)), RecordValues r) => Relvar a2 -> (a, a1, Relation r) -> IO () doUpdate rv ( updCount, totCount, updated ) = do rewriteRelvar rv updated putStrLn $ "Updated " ++ show updCount ++ " of " ++ show totCount ++ " tuples in " ++ relvarPath rv {-| Updates tuples of a relvar that match the given predicate. As SQL UPDATE. >>> update sp (\ [pun|pno|] -> pno == "P2" || pno == "P3" ) (\ [pun|qty|] -> _qty ( qty - 25 ) .*. emptyRecord) Updated 5 of 12 tuples in SuppliersPartsDB/SP.rv *SuppliersPartsExample> rPrint$ sp ┌─────┬─────┬─────┐ │ sno │ pno │ qty │ ╞═════╪═════╪═════╡ │ S1 │ P1 │ 300 │ │ S1 │ P2 │ 175 │ │ S1 │ P3 │ 375 │ │ S1 │ P4 │ 200 │ ... Note how the cardinality of the relvar will be equal or lower after an update: >>> assign sp sp' Value assigned to SuppliersPartsDB/SP.rv >>> count sp 12 >>> update sp (\[pun|pno|] -> pno == "P1" || pno == "P2" || pno == "P3") (\_ -> _pno "P1" .*. _qty 50 .*. emptyRecord) Updated 7 of 12 tuples in SuppliersPartsDB/SP.rv >>> count sp 9 -} -- TODO: Fix update count message to reflect the situation in the last example above, although this is tricky as this is most likely something that belongs naturally in the set level functions. Note however that it is not feasable to give update counts at all in RDBSMs, as keeping exact track of the cardinality of relvars constitutes an overhead that is in many cases unacceptable, and doesn't provide information that is as useful as a naïve mind might think anyhow. update :: (Ord (HList a), Read (HList (RecordValuesR a)), Show (HList (RecordValuesR a)), RecordValues a, HRearrange3 (LabelsOf a) (HAppendListR r r'2) a, HLabelSet (LabelsOf a), HLabelSet (LabelsOf (HAppendListR r r'2)), HDeleteLabels (LabelsOf r) a r'2, HMapAux HList TaggedFn (RecordValuesR a) a, HAppendList r r'2, SameLength' a (HAppendListR r r'2), SameLength' (LabelsOf a) (HAppendListR r r'2), SameLength' (HAppendListR r r'2) a, SameLength' (HAppendListR r r'2) (LabelsOf a), HAllTaggedLV (HAppendListR r r'2)) => Relvar a -> (Record a -> Bool) -> (Record a -> Record r) -> IO () update rv p f = do rv' <- readRelvar rv doUpdate rv ( update' rv' p f ) {-| Updates all tuples of a relvar. The second argument is a function that results in an attribute, making for a simpler function than for `update`. >>> updateA sp (\ [pun|pno|] -> pno == "P2" || pno == "P3" ) (\ [pun|qty|] -> _qty $ qty - 25) Updated 5 of 12 tuples in SuppliersPartsDB/SP.rv -} -- TODO: Can't get the type signature to compile, HUpdateAtLabel2 isn't exported from Data.HList.Record updateA rv p f = do rv' <- readRelvar rv doUpdate rv ( updateA' rv' p f ) doUpdateAll :: (Show a, Show (HList (RecordValuesR r)), RecordValues r) => Relvar a1 -> (a, Relation r) -> IO () doUpdateAll rv ( count, updated ) = do rewriteRelvar rv updated putStrLn $ "Updated " ++ show count ++ " tuples in " ++ relvarPath rv {-| Updates tuples of a relvar that match the given predicate. In SQL and Tutorial D both the predicate of @UPDATE@ is an optional clause, but optional clauses isn't idiomatic Haskell, hence this separate updateAll function. >>> updateAll sp (\ [pun|qty pno|] -> _qty ( qty - 25 ) .*. _pno ( pno ++ "X" ) .*. emptyRecord) Updated 12 tuples in SuppliersPartsDB/SP.rv *SuppliersPartsExample> pt sp ┌───────────────┬───────────────┬────────────────┐ │ sno :: String │ pno :: String │ qty :: Integer │ ╞═══════════════╪═══════════════╪════════════════╡ │ S1 │ P1X │ 275 │ ... -} updateAll :: (Ord (HList a), Read (HList (RecordValuesR a)), Show (HList (RecordValuesR a)), RecordValues a, HRearrange3 (LabelsOf a) (HAppendListR r r'2) a, HLabelSet (LabelsOf a), HLabelSet (LabelsOf (HAppendListR r r'2)), HDeleteLabels (LabelsOf r) a r'2, HMapAux HList TaggedFn (RecordValuesR a) a, HAppendList r r'2, SameLength' a (HAppendListR r r'2), SameLength' (LabelsOf a) (HAppendListR r r'2), SameLength' (HAppendListR r r'2) a, SameLength' (HAppendListR r r'2) (LabelsOf a), HAllTaggedLV (HAppendListR r r'2)) => Relvar a -> (Record a -> Record r) -> IO () updateAll rv f = do rv' <- readRelvar rv doUpdateAll rv (updateAll' rv' f) {-| Updates all tuples of a relvar. The second argument is a function that results in an attribute, making for a simpler function than for `updateAll`. >>> updateAllA sp (\ [pun|qty|] -> _qty $ qty - 50) Updated 12 tuples in SuppliersPartsDB/SP.rv >>> rPrint$ sp ┌───────────────┬───────────────┬────────────────┐ │ sno :: String │ pno :: String │ qty :: Integer │ ╞═══════════════╪═══════════════╪════════════════╡ │ S1 │ P1 │ 250 │ ... -} -- TODO: Can't get the type signature to compile, HUpdateAtLabel2 isn't visible from Data.HList.Record updateAllA rv f = do rv' <- readRelvar rv doUpdateAll rv (updateAllA' rv' f) -- == Deletes doDelete rv filtered nDeleted = do writeRelvarBody ( relvarPath rv ++ ".new" ) ( bodyAsList filtered ) renameFile ( relvarPath rv ++ ".new" ) ( relvarPath rv ) putStrLn $ "Deleted " ++ nDeleted ++ " tuples from " ++ relvarPath rv {-| Deletes a specified subset of a relvar. Note that this is not SQL DELETE, but instead a generalization thereof. See `Database.HaskRel.Relational.Expression.delete`. -} delete :: (Ord (HList t), Read (HList (RecordValuesR t)), Show (HList (RecordValuesR t)), RecordValues t, HMapAux HList TaggedFn (RecordValuesR t) t) => Relvar t -> Relation t -> IO () delete rv r = do rv' <- readRelvar rv let filtered = Data.Set.difference rv' r in doDelete rv filtered ( show $ size rv' - size filtered ) {-| Performs an inclusive delete against a relvar. Also not SQL DELETE. This will fail if the second argument is not a subset of the relation value identified by the relation variable reference. See `Database.HaskRel.Relational.Expression.iDelete`. -} iDelete :: (Ord (HList a), Ord (HList t), Read (HList (RecordValuesR t)), Show (HList (RecordValuesR t)), Typeable a, RecordValues a, RecordValues t, HRearrange3 (LabelsOf t) a t, HRearrange3 (LabelsOf a) t a, HLabelSet (LabelsOf t), HLabelSet (LabelsOf a), HMapAux HList TaggedFn (RecordValuesR t) t, HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR a) [[String]], SameLength' a t, SameLength' a (LabelsOf t), SameLength' t a, SameLength' t (LabelsOf a), SameLength' (LabelsOf t) a, SameLength' (LabelsOf a) t) => Relvar t -> Relation a -> IO () iDelete rv r = do rv' <- readRelvar rv let filtered = rv' `minus` r in if size filtered > ( size rv' - size r ) then error $ "Tuples not found in relvar " ++ relvarPath rv ++ ":\n" ++ showHRecSetTab ( r `minus` rv' ) else doDelete rv filtered ( show $ size rv' - size filtered ) {- | Delete by predicate, as SQL DELETE. >>> let newProd = relation [rTuple (pno .=. "P7", pName .=. "Baloon", color .=. "Red", weight .=. (-5 :: Rational), city .=. "Berlin")] >>> insert p newProd Inserted 1 of 1 tuples into SuppliersPartsDB/P.rv >>> deleteP p (\ [pun|pno|] -> pno == "P7" ) Deleted 1 tuples from SuppliersPartsDB/P.rv -} deleteP :: (Ord (HList t), Read (HList (RecordValuesR t)), Show (HList (RecordValuesR t)), RecordValues t, HMapAux HList TaggedFn (RecordValuesR t) t) => Relvar t -> (RTuple t -> Bool) -> IO () deleteP rv p = do rv' <- readRelvar rv let filtered = Data.Set.filter ( not . p ) rv' in doDelete rv filtered ( show $ size rv' - size filtered ) -- An iDeleteP function could also be defined, but its utility would be marginal.