opaleye-sqlite-0.0.0.0: src/Opaleye/SQLite/Internal/Values.hs
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
module Opaleye.SQLite.Internal.Values where
import qualified Opaleye.SQLite.PGTypes as T
import Opaleye.SQLite.Internal.Column (Column(Column))
import qualified Opaleye.SQLite.Internal.Unpackspec as U
import qualified Opaleye.SQLite.Internal.Tag as T
import qualified Opaleye.SQLite.Internal.PrimQuery as PQ
import qualified Opaleye.SQLite.Internal.PackMap as PM
import qualified Opaleye.SQLite.Internal.HaskellDB.PrimQuery as HPQ
import Data.Profunctor (Profunctor, dimap, rmap)
import Data.Profunctor.Product (ProductProfunctor, empty, (***!))
import qualified Data.Profunctor.Product as PP
import Data.Profunctor.Product.Default (Default, def)
import Control.Applicative (Applicative, pure, (<*>))
-- There are two annoyances with creating SQL VALUES statements
--
-- 1. SQL does not allow empty VALUES statements so if we want to
-- create a VALUES statement from an empty list we have to fake it
-- somehow. The current approach is to make a VALUES statement
-- with a single row of NULLs and then restrict it with WHERE
-- FALSE.
-- 2. Postgres's type inference of constants is pretty poor so we will
-- sometimes have to give explicit type signatures. The future
-- ShowConstant class will have the same problem. NB We don't
-- actually currently address this problem.
valuesU :: U.Unpackspec columns columns'
-> Valuesspec columns columns'
-> [columns]
-> ((), T.Tag) -> (columns', PQ.PrimQuery, T.Tag)
valuesU unpack valuesspec rows ((), t) = (newColumns, primQ', T.next t)
where runRow row = valuesRow
where (_, valuesRow) =
PM.run (U.runUnpackspec unpack extractValuesEntry row)
(newColumns, valuesPEs_nulls) =
PM.run (runValuesspec valuesspec (extractValuesField t))
valuesPEs = map fst valuesPEs_nulls
nulls = map snd valuesPEs_nulls
yieldNoRows :: PQ.PrimQuery -> PQ.PrimQuery
yieldNoRows = PQ.restrict (HPQ.ConstExpr (HPQ.BoolLit False))
values' :: [[HPQ.PrimExpr]]
(values', wrap) = if null rows
then ([nulls], yieldNoRows)
else (map runRow rows, id)
primQ' = wrap (PQ.Values valuesPEs values')
-- We don't actually use the return value of this. It might be better
-- to come up with another Applicative instance for specifically doing
-- what we need.
extractValuesEntry :: HPQ.PrimExpr -> PM.PM [HPQ.PrimExpr] HPQ.PrimExpr
extractValuesEntry pe = do
PM.write pe
return pe
extractValuesField :: T.Tag -> HPQ.PrimExpr
-> PM.PM [(HPQ.Symbol, HPQ.PrimExpr)] HPQ.PrimExpr
extractValuesField = PM.extractAttr "values"
newtype Valuesspec columns columns' =
Valuesspec (PM.PackMap HPQ.PrimExpr HPQ.PrimExpr () columns')
runValuesspec :: Applicative f => Valuesspec columns columns'
-> (HPQ.PrimExpr -> f HPQ.PrimExpr) -> f columns'
runValuesspec (Valuesspec v) f = PM.traversePM v f ()
instance Default Valuesspec (Column T.PGInt4) (Column T.PGInt4) where
def = Valuesspec (PM.PackMap (\f () -> fmap Column (f (HPQ.ConstExpr HPQ.NullLit))))
-- {
-- Boilerplate instance definitions. Theoretically, these are derivable.
instance Functor (Valuesspec a) where
fmap f (Valuesspec g) = Valuesspec (fmap f g)
instance Applicative (Valuesspec a) where
pure = Valuesspec . pure
Valuesspec f <*> Valuesspec x = Valuesspec (f <*> x)
instance Profunctor Valuesspec where
dimap _ g (Valuesspec q) = Valuesspec (rmap g q)
instance ProductProfunctor Valuesspec where
empty = PP.defaultEmpty
(***!) = PP.defaultProfunctorProduct
-- }