@@ -23,12 +23,22 @@
2323#-}
2424
2525module Squeal.PostgreSQL.PG
26− ( PG
26+ ( -- * PG embeddings
27+ PG
2728 , NullPG
2829 , TuplePG
2930 , RowPG
3031 , Entity
32+ -- * Storage newtypes
33+ , Money (..)
34+ , Json (..)
35+ , Jsonb (..)
36+ , Composite (..)
37+ , Enumerated (..)
38+ , VarArray (..)
39+ , FixArray (..)
3140 , SOP.P (..)
41+ -- * Type families
3242 , LabelsPG
3343 , DimPG
3444 , FixPG
@@ -44,6 +54,7 @@ import Data.Kind (Type)
4454import Data.Int (Int16, Int32, Int64)
4555import Data.Scientific (Scientific)
4656import Data.Time (Day, DiffTime, LocalTime, TimeOfDay, TimeZone, UTCTime)
57+import Data.Vector (Vector)
4758import Data.Word (Word16, Word32, Word64)
4859import Data.UUID.Types (UUID)
4960import GHC.TypeLits
@@ -53,6 +64,7 @@ import qualified Data.ByteString.Lazy as Lazy (ByteString)
5364import qualified Data.ByteString as Strict (ByteString)
5465import qualified Data.Text.Lazy as Lazy (Text)
5566import qualified Data.Text as Strict (Text)
67+import qualified GHC.Generics as GHC
5668import qualified Generics.SOP as SOP
5769import qualified Generics.SOP.Record as SOP
5870import qualified Generics.SOP.Type.Metadata as Type
@@ -295,3 +307,72 @@ type family FixPG (hask :: Type) :: NullityType where
295307 FixPG (x,x,x,x,x,x,x,x,x,x) = FixPG x
296308 FixPG (x,x,x,x,x,x,x,x,x,x,x) = FixPG x
297309 FixPG x = NullPG x
310+
311+{- | The `Money` newtype stores a monetary value in terms
312+of the number of cents, i.e. @$2,000.20@ would be expressed as
313+@Money { cents = 200020 }@.
314+
315+>>> import Control.Monad (void)
316+>>> import Control.Monad.IO.Class (liftIO)
317+>>> import Squeal.PostgreSQL
318+>>> :{
319+let
320+ roundTrip :: Query_ (Public '[]) (Only Money) (Only Money)
321+ roundTrip = values_ $ parameter @1 money `as` #fromOnly
322+:}
323+
324+>>> let input = Only (Money 20020)
325+
326+>>> :{
327+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
328+ result <- runQueryParams roundTrip input
329+ Just output <- firstRow result
330+ liftIO . print $ input == output
331+:}
332+True
333+-}
334+newtype Money = Money { cents :: Int64 }
335+ deriving (Eq, Ord, Show, Read, GHC.Generic)
336+type instance PG Money = 'PGmoney
337+
338+{- | The `Json` newtype is an indication that the Haskell
339+type it's applied to should be stored as a `PGjson`.
340+-}
341+newtype Json hask = Json {getJson :: hask}
342+ deriving (Eq, Ord, Show, Read, GHC.Generic)
343+type instance PG (Json hask) = 'PGjson
344+
345+{- | The `Jsonb` newtype is an indication that the Haskell
346+type it's applied to should be stored as a `PGjsonb`.
347+-}
348+newtype Jsonb hask = Jsonb {getJsonb :: hask}
349+ deriving (Eq, Ord, Show, Read, GHC.Generic)
350+type instance PG (Jsonb hask) = 'PGjsonb
351+
352+{- | The `Composite` newtype is an indication that the Haskell
353+type it's applied to should be stored as a `PGcomposite`.
354+-}
355+newtype Composite record = Composite {getComposite :: record}
356+ deriving (Eq, Ord, Show, Read, GHC.Generic)
357+type instance PG (Composite hask) = 'PGcomposite (RowPG hask)
358+
359+{- | The `Enumerated` newtype is an indication that the Haskell
360+type it's applied to should be stored as a `PGenum`.
361+-}
362+newtype Enumerated enum = Enumerated {getEnumerated :: enum}
363+ deriving (Eq, Ord, Show, Read, GHC.Generic)
364+type instance PG (Enumerated hask) = 'PGenum (LabelsPG hask)
365+
366+{- | The `VarArray` newtype is an indication that the Haskell
367+type it's applied to should be stored as a `PGvararray`.
368+-}
369+newtype VarArray arr = VarArray {getVarArray :: arr}
370+ deriving (Eq, Ord, Show, Read, GHC.Generic)
371+type instance PG (VarArray (Vector hask)) = 'PGvararray (NullPG hask)
372+
373+{- | The `FixArray` newtype is an indication that the Haskell
374+type it's applied to should be stored as a `PGfixarray`.
375+-}
376+newtype FixArray arr = FixArray {getFixArray :: arr}
377+ deriving (Eq, Ord, Show, Read, GHC.Generic)
378+type instance PG (FixArray x) = 'PGfixarray (DimPG x) (FixPG x)