module Database.CQL.Protocol.Tuple.TH where
import Control.Applicative
import Control.Monad
import Data.Functor.Identity
import Data.Serialize
import Data.Vector (Vector, (!?))
import Data.Word
import Database.CQL.Protocol.Class
import Database.CQL.Protocol.Codec (putValue, getValue)
import Database.CQL.Protocol.Types
import Language.Haskell.TH
import Prelude
import qualified Data.Vector as Vec
data Row = Row
{ types :: !([ColumnType])
, values :: !(Vector Value)
} deriving (Eq, Show)
fromRow :: Cql a => Int -> Row -> Either String a
fromRow i r =
case values r !? i of
Nothing -> Left "out of bounds access"
Just v -> fromCql v
mkRow :: [(Value, ColumnType)] -> Row
mkRow xs = let (v, t) = unzip xs in Row t (Vec.fromList v)
rowLength :: Row -> Int
rowLength r = Vec.length (values r)
columnTypes :: Row -> [ColumnType]
columnTypes = types
class PrivateTuple a where
count :: Tagged a Int
check :: Tagged a ([ColumnType] -> [ColumnType])
tuple :: Version -> [ColumnType] -> Get a
store :: Version -> Putter a
class PrivateTuple a => Tuple a
instance PrivateTuple () where
count = Tagged 0
check = Tagged $ const []
tuple _ _ = return ()
store _ = const $ return ()
instance Tuple ()
instance Cql a => PrivateTuple (Identity a) where
count = Tagged 1
check = Tagged $ typecheck [untag (ctype :: Tagged a ColumnType)]
tuple v _ = Identity <$> element v ctype
store v (Identity a) = do
put (1 :: Word16)
putValue v (toCql a)
instance Cql a => Tuple (Identity a)
instance PrivateTuple Row where
count = Tagged (1)
check = Tagged $ const []
tuple v t = Row t . Vec.fromList <$> mapM (getValue v) t
store v r = do
put (fromIntegral (rowLength r) :: Word16)
Vec.mapM_ (putValue v) (values r)
instance Tuple Row
genInstances :: Int -> Q [Dec]
genInstances n = join <$> mapM tupleInstance [2 .. n]
tupleInstance :: Int -> Q [Dec]
tupleInstance n = do
let cql = mkName "Cql"
vnames <- replicateM n (newName "a")
let vtypes = map VarT vnames
let tupleType = foldl1 ($:) (TupleT n : vtypes)
#if MIN_VERSION_template_haskell(2,10,0)
let ctx = map (AppT (ConT cql)) vtypes
#else
let ctx = map (\t -> ClassP cql [t]) vtypes
#endif
td <- tupleDecl n
sd <- storeDecl n
return
#if MIN_VERSION_template_haskell(2,11,0)
[ InstanceD Nothing ctx (tcon "PrivateTuple" $: tupleType)
#else
[ InstanceD ctx (tcon "PrivateTuple" $: tupleType)
#endif
[ FunD (mkName "count") [countDecl n]
, FunD (mkName "check") [checkDecl vnames]
, FunD (mkName "tuple") [td]
, FunD (mkName "store") [sd]
]
#if MIN_VERSION_template_haskell(2,11,0)
, InstanceD Nothing ctx (tcon "Tuple" $: tupleType) []
#else
, InstanceD ctx (tcon "Tuple" $: tupleType) []
#endif
]
countDecl :: Int -> Clause
countDecl n = Clause [] (NormalB body) []
where
body = con "Tagged" $$ litInt n
checkDecl :: [Name] -> Clause
checkDecl names = Clause [] (NormalB body) []
where
body = con "Tagged" $$ (var "typecheck" $$ ListE (map fn names))
fn n = var "untag" $$ SigE (var "ctype") (tty n)
tty n = tcon "Tagged" $: VarT n $: tcon "ColumnType"
tupleDecl :: Int -> Q Clause
tupleDecl n = do
let v = mkName "v"
Clause [VarP v, WildP] (NormalB $ body v) <$> comb
where
body v = UInfixE (var "combine") (var "<$>") (foldl1 star (elts v))
elts v = replicate n (var "element" $$ VarE v $$ var "ctype")
star = flip UInfixE (var "<*>")
comb = do
names <- replicateM n (newName "x")
let f = NormalB $ TupE (map VarE names)
return [ FunD (mkName "combine") [Clause (map VarP names) f []] ]
storeDecl :: Int -> Q Clause
storeDecl n = do
let v = mkName "v"
names <- replicateM n (newName "k")
return $ Clause [VarP v, TupP (map VarP names)] (NormalB $ body v names) []
where
body x names = DoE (NoBindS size : map (NoBindS . value x) names)
size = var "put" $$ SigE (litInt n) (tcon "Word16")
value x v = var "putValue" $$ VarE x $$ (var "toCql" $$ VarE v)
litInt :: Integral i => i -> Exp
litInt = LitE . IntegerL . fromIntegral
var, con :: String -> Exp
var = VarE . mkName
con = ConE . mkName
tcon :: String -> Type
tcon = ConT . mkName
($$) :: Exp -> Exp -> Exp
($$) = AppE
($:) :: Type -> Type -> Type
($:) = AppT
element :: Cql a => Version -> Tagged a ColumnType -> Get a
element v t = getValue v (untag t) >>= either fail return . fromCql
typecheck :: [ColumnType] -> [ColumnType] -> [ColumnType]
typecheck rr cc = if checkAll (===) rr cc then [] else rr
where
checkAll f as bs = and (zipWith f as bs)
checkField (a, b) (c, d) = a == c && b === d
TextColumn === VarCharColumn = True
VarCharColumn === TextColumn = True
(MaybeColumn a) === b = a === b
(ListColumn a) === (ListColumn b) = a === b
(SetColumn a) === (SetColumn b) = a === b
(MapColumn a b) === (MapColumn c d) = a === c && b === d
(UdtColumn a as) === (UdtColumn b bs) = a == b && checkAll checkField as bs
(TupleColumn as) === (TupleColumn bs) = checkAll (===) as bs
a === b = a == b