Skip to content

Commit 97dcac4

Browse files
committed
Merge branch 'release/2.3.0' into production
2 parents 47eea18 + fc7d4f5 commit 97dcac4

File tree

10 files changed

+427
-226
lines changed

10 files changed

+427
-226
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ cabal.sandbox.config
44
*~
55
TAGS
66
cabal.config
7+
.stack-work

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## 2.3.0
4+
### Added
5+
* `derivePgEnum` TH generator for enum fields
6+
### Changed
7+
* TH code splitted to modules
8+
39
## 2.2.0
410
### Added
511
* `MonadHReader` instance for `PgMonadT`

example/Main.hs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module Main where
2+
3+
import Data.Text (Text)
4+
import Data.Time
5+
import Database.PostgreSQL.Query
6+
import Text.Inflections
7+
8+
-- | Example enum type to check out how 'derivePgEnum' works
9+
data Species
10+
= Dog
11+
| Cat
12+
| Snake
13+
14+
derivePgEnum toUnderscore ''Species
15+
16+
-- | Example structure to check out how 'deriveFromRow' works
17+
data AnimalInfo = AnimalInfo
18+
{ _aiName :: Text
19+
, _aiSpecies :: Species
20+
, _aiBirtDay :: UTCTime
21+
}
22+
23+
deriveFromRow ''AnimalInfo
24+
deriveToRow ''AnimalInfo
25+
26+
main :: IO ()
27+
main = do
28+
return ()

postgresql-query.cabal

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: postgresql-query
2-
version: 2.2.0
2+
version: 2.3.0
33

44
synopsis: Sql interpolating quasiquote plus some kind of primitive ORM
55
using it
@@ -29,6 +29,10 @@ library
2929
, Database.PostgreSQL.Query.Internal
3030
, Database.PostgreSQL.Query.SqlBuilder
3131
, Database.PostgreSQL.Query.TH
32+
, Database.PostgreSQL.Query.TH.Common
33+
, Database.PostgreSQL.Query.TH.Entity
34+
, Database.PostgreSQL.Query.TH.Enum
35+
, Database.PostgreSQL.Query.TH.Row
3236
, Database.PostgreSQL.Query.TH.SqlExp
3337
, Database.PostgreSQL.Query.Types
3438

@@ -69,6 +73,7 @@ library
6973
, haskell-src-meta
7074
, hreader >= 1.0.0 && < 2.0.0
7175
, hset >= 2.0.0 && < 3.0.0
76+
, inflections >= 0.2 && < 0.3
7277
, monad-control == 0.3.3.1 || > 1.0.0.3
7378
, monad-logger
7479
, mtl
@@ -110,4 +115,21 @@ test-suite test
110115
, tasty-hunit
111116
, tasty-quickcheck
112117
, tasty-th
113-
, text
118+
, text
119+
120+
test-suite example
121+
type: exitcode-stdio-1.0
122+
default-language: Haskell2010
123+
ghc-options: -Wall
124+
hs-source-dirs: example
125+
main-is: Main.hs
126+
127+
default-extensions: FlexibleInstances
128+
, OverloadedStrings
129+
, TemplateHaskell
130+
131+
build-depends: base
132+
, inflections
133+
, postgresql-query
134+
, text
135+
, time

src/Database/PostgreSQL/Query/TH.hs

Lines changed: 11 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -1,218 +1,20 @@
11
module Database.PostgreSQL.Query.TH
2-
( -- * Deriving instances
3-
deriveFromRow
4-
, deriveToRow
5-
, deriveEntity
6-
, deriveEverything
7-
, EntityOptions(..)
8-
-- * Embedding sql files
9-
, embedSql
10-
, sqlFile
11-
-- * Sql string interpolation
12-
, sqlExp
13-
, sqlExpEmbed
14-
, sqlExpFile
15-
) where
2+
( -- * Deriving instances
3+
deriveEverything
4+
5+
, module Database.PostgreSQL.Query.TH.Entity
6+
, module Database.PostgreSQL.Query.TH.Enum
7+
, module Database.PostgreSQL.Query.TH.Row
8+
, module Database.PostgreSQL.Query.TH.SqlExp
9+
) where
1610

1711
import Prelude
1812

19-
import Control.Applicative
20-
import Control.Monad
21-
import Data.Default
22-
import Data.FileEmbed ( embedFile )
23-
import Data.String
24-
import Database.PostgreSQL.Query.Entity ( Entity(..) )
13+
import Database.PostgreSQL.Query.TH.Entity
14+
import Database.PostgreSQL.Query.TH.Enum
15+
import Database.PostgreSQL.Query.TH.Row
2516
import Database.PostgreSQL.Query.TH.SqlExp
26-
import Database.PostgreSQL.Query.Types ( FN(..) )
27-
import Database.PostgreSQL.Simple.FromRow ( FromRow(..), field )
28-
import Database.PostgreSQL.Simple.ToRow ( ToRow(..) )
29-
import Database.PostgreSQL.Simple.Types ( Query(..) )
3017
import Language.Haskell.TH
31-
import Language.Haskell.TH.Syntax
32-
33-
-- | Return constructor name
34-
cName :: (Monad m) => Con -> m Name
35-
cName (NormalC n _) = return n
36-
cName (RecC n _) = return n
37-
cName _ = error "Constructor must be simple"
38-
39-
-- | Return count of constructor fields
40-
cArgs :: (Monad m) => Con -> m Int
41-
cArgs (NormalC _ n) = return $ length n
42-
cArgs (RecC _ n) = return $ length n
43-
cArgs _ = error "Constructor must be simple"
44-
45-
-- | Get field names from record constructor
46-
cFieldNames :: Con -> [Name]
47-
cFieldNames (RecC _ vst) = map (\(a, _, _) -> a) vst
48-
cFieldNames _ = error "Constructor must be a record (product type with field names)"
49-
50-
{-| Derive 'FromRow' instance. i.e. you have type like that
51-
52-
@
53-
data Entity = Entity
54-
{ eField :: Text
55-
, eField2 :: Int
56-
, efield3 :: Bool }
57-
@
58-
59-
then 'deriveFromRow' will generate this instance:
60-
instance FromRow Entity where
61-
62-
@
63-
instance FromRow Entity where
64-
fromRow = Entity
65-
\<$> field
66-
\<*> field
67-
\<*> field
68-
@
69-
70-
Datatype must have just one constructor with arbitrary count of fields
71-
-}
72-
73-
deriveFromRow :: Name -> Q [Dec]
74-
deriveFromRow t = do
75-
TyConI (DataD _ _ _ [con] _) <- reify t
76-
cname <- cName con
77-
cargs <- cArgs con
78-
[d|instance FromRow $(return $ ConT t) where
79-
fromRow = $(fieldsQ cname cargs)|]
80-
where
81-
fieldsQ cname cargs = do
82-
fld <- [| field |]
83-
fmp <- [| (<$>) |]
84-
fap <- [| (<*>) |]
85-
return $ UInfixE (ConE cname) fmp (fapChain cargs fld fap)
86-
87-
fapChain 0 _ _ = error "there must be at least 1 field in constructor"
88-
fapChain 1 fld _ = fld
89-
fapChain n fld fap = UInfixE fld fap (fapChain (n-1) fld fap)
90-
91-
lookupVNameErr :: String -> Q Name
92-
lookupVNameErr name =
93-
lookupValueName name >>=
94-
maybe (error $ "could not find identifier: " ++ name)
95-
return
96-
97-
98-
{-| derives 'ToRow' instance for datatype like
99-
100-
@
101-
data Entity = Entity
102-
{ eField :: Text
103-
, eField2 :: Int
104-
, efield3 :: Bool }
105-
@
106-
107-
it will derive instance like that:
108-
109-
@
110-
instance ToRow Entity where
111-
toRow (Entity e1 e2 e3) =
112-
[ toField e1
113-
, toField e2
114-
, toField e3 ]
115-
@
116-
-}
117-
118-
deriveToRow :: Name -> Q [Dec]
119-
deriveToRow t = do
120-
TyConI (DataD _ _ _ [con] _) <- reify t
121-
cname <- cName con
122-
cargs <- cArgs con
123-
cvars <- sequence
124-
$ replicate cargs
125-
$ newName "a"
126-
[d|instance ToRow $(return $ ConT t) where
127-
toRow $(return $ ConP cname $ map VarP cvars) = $(toFields cvars)|]
128-
where
129-
toFields v = do
130-
tof <- lookupVNameErr "toField"
131-
return $ ListE
132-
$ map
133-
(\e -> AppE (VarE tof) (VarE e))
134-
v
135-
136-
-- | Options for deriving `Entity`
137-
data EntityOptions = EntityOptions
138-
{ eoTableName :: String -> String -- ^ Type name to table name converter
139-
, eoColumnNames :: String -> String -- ^ Record field to column name converter
140-
, eoDeriveClasses :: [Name] -- ^ Typeclasses to derive for Id
141-
, eoIdType :: Name -- ^ Base type for Id
142-
}
143-
144-
instance Default EntityOptions where
145-
def = EntityOptions
146-
{ eoTableName = id
147-
, eoColumnNames = id
148-
, eoDeriveClasses = [''Ord, ''Eq, ''Show]
149-
, eoIdType = ''Integer
150-
}
151-
152-
{- | Derives instance for 'Entity' using type name and field names. Also
153-
generates type synonim for ID. E.g. code like this:
154-
155-
@
156-
data Agent = Agent
157-
{ aName :: !Text
158-
, aAttributes :: !HStoreMap
159-
, aLongWeirdName :: !Int
160-
} deriving (Ord, Eq, Show)
161-
162-
$(deriveEntity
163-
def { eoIdType = ''Id
164-
, eoTableName = toUnderscore
165-
, eoColumnNames = toUnderscore . drop 1
166-
, eoDeriveClasses =
167-
[''Show, ''Read, ''Ord, ''Eq
168-
, ''FromField, ''ToField, ''PathPiece]
169-
}
170-
''Agent )
171-
@
172-
173-
Will generate code like this:
174-
175-
@
176-
instance Database.PostgreSQL.Query.Entity Agent where
177-
newtype EntityId Agent
178-
= AgentId {getAgentId :: Id}
179-
deriving (Show, Read, Ord, Eq, FromField, ToField, PathPiece)
180-
tableName _ = "agent"
181-
fieldNames _ = ["name", "attributes", "long_weird_name"]
182-
type AgentId = EntityId Agent
183-
@
184-
185-
So, you dont need to write it by hands any more.
186-
187-
NOTE: 'toUnderscore' is from package 'inflections' here
188-
-}
189-
190-
deriveEntity :: EntityOptions -> Name -> Q [Dec]
191-
deriveEntity opts tname = do
192-
TyConI (DataD _ _ _ [tcon] _) <- reify tname
193-
econt <- [t|Entity $(conT tname)|]
194-
ConT entityIdName <- [t|EntityId|]
195-
let tnames = nameBase tname
196-
idname = tnames ++ "Id"
197-
unidname = "get" ++ idname
198-
idtype = ConT (eoIdType opts)
199-
idcon = RecC (mkName idname)
200-
[(mkName unidname, NotStrict, idtype)]
201-
iddec = NewtypeInstD [] entityIdName [ConT tname]
202-
idcon (eoDeriveClasses opts)
203-
tblName = fromString $ eoTableName opts tnames
204-
fldNames = map (fromString . eoColumnNames opts . nameBase)
205-
$ cFieldNames tcon
206-
VarE ntableName <- [e|tableName|]
207-
VarE nfieldNames <- [e|fieldNames|]
208-
tblExp <- lift (tblName :: FN)
209-
fldExp <- mapM lift (fldNames :: [FN])
210-
let tbldec = FunD ntableName [Clause [WildP] (NormalB tblExp) []]
211-
flddec = FunD nfieldNames [Clause [WildP] (NormalB $ ListE fldExp) []]
212-
ret = InstanceD [] econt
213-
[ iddec, tbldec, flddec ]
214-
syndec = TySynD (mkName idname) [] (AppT (ConT entityIdName) (ConT tname))
215-
return [ret, syndec]
21618

21719
{- | Calls sequently `deriveFromRow` `deriveToRow` `deriveEntity`. E.g. code like this:
21820
@@ -261,18 +63,3 @@ deriveEverything opts tname = fmap concat $ sequence
26163
[ deriveToRow tname
26264
, deriveFromRow tname
26365
, deriveEntity opts tname ]
264-
265-
-- embed sql file as value
266-
embedSql :: String -- ^ File path
267-
-> Q Exp
268-
embedSql path = do
269-
[e| (Query ( $(embedFile path) )) |]
270-
{-# DEPRECATED embedSql "use 'sqlExpEmbed' instead" #-}
271-
272-
-- embed sql file by pattern. __sqlFile "dir/file"__ is just the same as
273-
-- __embedSql "sql/dir/file.sql"__
274-
sqlFile :: String -- ^ sql file pattern
275-
-> Q Exp
276-
sqlFile s = do
277-
embedSql $ "sql/" ++ s ++ ".sql"
278-
{-# DEPRECATED sqlFile "use 'sqlExpFile' instead" #-}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module Database.PostgreSQL.Query.TH.Common
2+
( cName
3+
, cArgs
4+
, cFieldNames
5+
, lookupVNameErr
6+
) where
7+
8+
import Prelude
9+
10+
import Language.Haskell.TH
11+
12+
-- | Return constructor name
13+
cName :: (Monad m) => Con -> m Name
14+
cName (NormalC n _) = return n
15+
cName (RecC n _) = return n
16+
cName _ = error "Constructor must be simple"
17+
18+
-- | Return count of constructor fields
19+
cArgs :: (Monad m) => Con -> m Int
20+
cArgs (NormalC _ n) = return $ length n
21+
cArgs (RecC _ n) = return $ length n
22+
cArgs _ = error "Constructor must be simple"
23+
24+
-- | Get field names from record constructor
25+
cFieldNames :: Con -> [Name]
26+
cFieldNames (RecC _ vst) = map (\(a, _, _) -> a) vst
27+
cFieldNames _ = error "Constructor must be a record (product type with field names)"
28+
29+
30+
lookupVNameErr :: String -> Q Name
31+
lookupVNameErr name =
32+
lookupValueName name >>=
33+
maybe (error $ "could not find identifier: " ++ name)
34+
return

0 commit comments

Comments
 (0)