{-# LANGUAGE TypeFamilies, TypeOperators, ConstraintKinds, FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Convenience facilities for working with nullable columns. module Database.Selda.Nullable ( NonNull, (:?~) , nonNull, restrict', (?!) , (?==), (?/=), (?>), (?<), (?>=), (?<=), (?+), (?-), (?*), (?/) ) where import Database.Selda ( SqlType, Same, Row, Col, Selector, Coalesce, Query, restrict, SqlOrd, (.==), (./=), (.>), (.<), (.>=), (.<=), isNull, not_ ) import Database.Selda.Unsafe (cast) import Database.Selda.Selectors ( Selector(selectorIndex) ) import Database.Selda.Column ( UntypedCol(Untyped), Row(Many), Col(One) ) import Unsafe.Coerce ( unsafeCoerce ) -- | Two SQL types which are identical modulo nullability. type a :?~ b = ( NonNull a ~ NonNull b , SqlType (NonNull a) , SqlType (NonNull b) ) type family NonNull a where NonNull (Maybe a) = a NonNull a = a -- | Unconditionally convert a nullable value into a non-nullable one, -- using the standard SQL null-coalescing behavior. fromNullable :: SqlType (NonNull a) => Col s a -> Col s (NonNull a) fromNullable = unsafeCoerce (?==), (?/=) :: (a :?~ b, SqlType a, Same s t) => Col s a -> Col t b -> Col s (Maybe Bool) (?>), (?<), (?>=), (?<=) :: (a :?~ b, SqlOrd (NonNull a), Same s t) => Col s a -> Col t b -> Col s (Maybe Bool) (?+), (?-), (?*) :: (a :?~ b, Num (NonNull a), Same s t) => Col s a -> Col t b -> Col s (Maybe (NonNull a)) (?/) :: (a :?~ b, Fractional (Col s (NonNull a)), Same s t) => Col s a -> Col t b -> Col s (Maybe (NonNull a)) a ?== b = cast $ fromNullable a .== fromNullable b a ?/= b = cast $ fromNullable a ./= fromNullable b a ?> b = cast $ fromNullable a .> fromNullable b a ?< b = cast $ fromNullable a .< fromNullable b a ?>= b = cast $ fromNullable a .>= fromNullable b a ?<= b = cast $ fromNullable a .<= fromNullable b a ?+ b = cast $ fromNullable a + fromNullable b a ?- b = cast $ fromNullable a - fromNullable b a ?* b = cast $ fromNullable a * fromNullable b a ?/ b = cast $ fromNullable a / fromNullable b infixl 4 ?== infixl 4 ?/= infixl 4 ?> infixl 4 ?< infixl 4 ?>= infixl 4 ?<= infixl 6 ?+ infixl 6 ?- infixl 7 ?* infixl 7 ?/ -- | Selector indexing, overloaded to work on nullable as well as non-nullable -- rows. (?!) :: forall s t a. SqlType a => Row s t -> Selector (NonNull t) a -> Col s (Coalesce (Maybe a)) (Many xs) ?! i = case xs !! (selectorIndex i) of Untyped x -> One (unsafeCoerce x) infixl 9 ?! -- | Converts a nullable column into a non-nullable one, yielding the empty -- result set if the column is null. nonNull :: (Same s t, SqlType a) => Col s (Maybe a) -> Query t (Col t a) nonNull x = do restrict (not_ $ isNull x) return (fromNullable x) -- | Restrict a query using a nullable expression. -- Equivalent to @restrict . ifNull false@. restrict' :: Same s t => Col s (Maybe Bool) -> Query t () restrict' = restrict . fromNullable