Skip to content

Commit 7cbef06

Browse files
committed
Fix accidental FromDbValues instances (#25)
1 parent e97d926 commit 7cbef06

6 files changed

Lines changed: 53 additions & 27 deletions

File tree

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ Database-agnostic interface to generically persisted data.
55
## Introduction
66

77
Explanation of the above:
8-
- Database-agnostic interface: the interface is called `MonadDb`, and you must
9-
specify how it can communicate with your database (e.g. PostgreSQL server).
10-
- Generically persisted data: you can derive the necessary instances for your
11-
data types via `Generics`. This will enable `MonadDb` to read/write instances
12-
of your data types to/from your database.
8+
- Database-agnostic: the typeclass is called `MonadDb`, and you must specify how
9+
an instance can communicate with your database. We provide an example for
10+
connecting to Postgres in the [runnable tutorial](tutorial/tutorial/Main.hs).
11+
- Generically persisted data: you can derive the necessary instances in one line
12+
via `Generics`, to enable `MonadDb` to read/write instances of your data types
13+
to/from your database.
1314

1415
A key intended feature of this library is that the typeclass `MonadDb` can be
1516
used either server-side or client-side. Allowing your client application (e.g.
@@ -22,7 +23,8 @@ to your database without having to write the usual server boilerplate.
2223

2324
## Quick Start
2425

25-
A tutorial as code exists [here](tutorial/tutorial/Main.hs).
26+
The [runnable tutorial](tutorial/tutorial/Main.hs) is the recommended way of
27+
becoming familiar with `database-generic`.
2628

2729
To run the tutorial on your machine:
2830
1. Clone this repo.

database-generic/src/Database/Generic/Entity/DbTypes.hs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ module Database.Generic.Entity.DbTypes where
55
import Data.Aeson qualified as Aeson
66
import Data.ByteString (ByteString)
77
import Data.ByteString.Char8 qualified as BS
8-
import Database.Generic.Entity.FromDb (FromDbValues(..))
98
import Database.Generic.Prelude
109
import Database.HDBC qualified as HDBC
1110

1211
data DbT f
13-
= DbBytes !(F f Bytes)
12+
= DbBool !(F f Bool)
13+
| DbBytes !(F f Bytes)
1414
| DbInt64 !(F f Int64)
1515
| DbInteger !(F f Integer)
1616
| DbString !(F f String)
@@ -32,6 +32,9 @@ deriving instance Show (DbT Unit)
3232
class HasDbType a where
3333
dbType :: DbType
3434

35+
instance HasDbType Bool where
36+
dbType = DbBool Unit
37+
3538
instance HasDbType Int64 where
3639
dbType = DbInt64 Unit
3740

@@ -46,20 +49,12 @@ deriving instance Aeson.ToJSON (DbT Id)
4649
deriving instance Eq (DbT Id)
4750
deriving instance Show (DbT Id)
4851

49-
instance From Int64 DbValue where from = DbInt64
52+
instance From Bool DbValue where from = DbBool
53+
instance From Int64 DbValue where from = DbInt64
5054
instance From String DbValue where from = DbString
5155

52-
instance FromDbValues DbValue Int64 where
53-
fromDbValues [DbInt64 i] = i
54-
fromDbValues [DbInteger i] = unsafeFrom i
55-
fromDbValues x = error $ "Error constructing Int64 from " <> show x
56-
57-
instance FromDbValues DbValue String where
58-
fromDbValues [DbBytes b] = from b
59-
fromDbValues [DbString s] = s
60-
fromDbValues x = error $ "Error constructing Int64 from " <> show x
61-
6256
instance From HDBC.SqlValue DbValue where
57+
from (HDBC.SqlBool b) = DbBool b
6358
from (HDBC.SqlString s) = DbString s
6459
from (HDBC.SqlByteString b) = DbBytes $ Bytes b
6560
from (HDBC.SqlInt64 i) = DbInt64 i

database-generic/src/Database/Generic/Entity/FromDb.hs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
module Database.Generic.Entity.FromDb where
44

5+
import Database.Generic.Entity.DbColumns (HasDbColumns)
6+
import Database.Generic.Entity.DbTypes (DbT(..), DbValue)
57
import Database.Generic.Prelude
68
import Generics.Eot qualified as G
79

@@ -17,7 +19,26 @@ instance (Show dbv, Typeable dbv) => Exception (FromDbError dbv)
1719
class FromDbValues dbv a where
1820
fromDbValues :: [dbv] -> a
1921

20-
instance {-# OVERLAPPABLE #-} (G.HasEot a, GFromDbValues dbv (G.Eot a)) => FromDbValues dbv a where
22+
instance FromDbValues DbValue Bool where
23+
fromDbValues [DbBool b] = b
24+
fromDbValues x = error $ "Error constructing Bool from " <> show x
25+
26+
instance FromDbValues DbValue Int64 where
27+
fromDbValues [DbInt64 i] = i
28+
fromDbValues [DbInteger i] = unsafeFrom i
29+
fromDbValues x = error $ "Error constructing Int64 from " <> show x
30+
31+
instance FromDbValues DbValue String where
32+
fromDbValues [DbBytes b] = from b
33+
fromDbValues [DbString s] = s
34+
fromDbValues x = error $ "Error constructing Int64 from " <> show x
35+
36+
instance {-# OVERLAPPABLE #-}
37+
( G.HasEot a
38+
, GFromDbValues dbv (G.Eot a)
39+
, HasDbColumns a -- Only included to ensure that 'FromDbValues' instances aren't
40+
-- derived for simple datatypes such as 'Bool'.
41+
) => FromDbValues dbv a where
2142
fromDbValues = G.fromEot . gFromDbValues
2243

2344
-- | Typeclass for generic implementation of 'FromDbValues'.

database-generic/src/Database/Generic/Entity/ToDb.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
module Database.Generic.Entity.ToDb where
44

5+
import Database.Generic.Entity.DbColumns (HasDbColumns)
56
import Database.Generic.Entity.DbTypes (DbValue)
67
import Database.Generic.Prelude
78
import Generics.Eot qualified as G
@@ -21,7 +22,12 @@ instance {-# OVERLAPPABLE #-} From a DbValue => ToDbValue a where
2122
class ToDbValues a where
2223
toDbValues :: a -> [DbValue]
2324

24-
instance {-# OVERLAPPABLE #-} (G.HasEot a, GToDbValues (G.Eot a)) => ToDbValues a where
25+
instance {-# OVERLAPPABLE #-}
26+
( G.HasEot a
27+
, GToDbValues (G.Eot a)
28+
, HasDbColumns a -- Only included to ensure that 'ToDbValues' instances aren't
29+
-- derived for simple datatypes such as 'Bool'.
30+
) => ToDbValues a where
2531
toDbValues = gToDbValues . G.toEot
2632

2733
-- | Typeclass for generic implementation of 'ToDbValues'.

database-generic/src/Database/Generic/Serialize.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ class Serialize a db where
1111
serialize :: a -> String
1212

1313
instance Serialize DbType PostgreSQL where
14+
serialize (DbBool Unit) = "BOOLEAN"
1415
serialize (DbBytes Unit) = "BINARY"
1516
serialize (DbInt64 Unit) = "BIGINT"
1617
serialize (DbInteger Unit) = "BIGINT"
1718
serialize (DbString Unit) = "VARCHAR"
1819

1920
instance Serialize DbValue PostgreSQL where
21+
serialize (DbBool b) = show b
2022
serialize (DbBytes b) = show b
2123
serialize (DbInt64 i) = show i
2224
serialize (DbInteger i) = show i

tutorial/tutorial/Main.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import GHC.Generics (Generic)
2525
import Witch (from)
2626

2727
-- | Data type we want to persist.
28-
data Person = Person { age :: !Int64, name :: !String }
28+
data Person = Person { age :: !Int64, name :: !String, ownsDog :: !Bool }
2929
deriving (Generic, PrimaryKey "name", Show)
3030

3131
-- | Connection string to access our PostgreSQL DB.
@@ -68,24 +68,24 @@ instance MonadDbNewConn AppM PSQL.Connection where
6868
main :: IO ()
6969
main = do
7070
let c = connStr "127.0.0.1" 5432 "postgres" "demo" "demo"
71-
let john = Person 70 "John"
71+
let john = Person 70 "John" False
7272
let info m s = do
7373
putStrLn $ "\n" <> m
7474
print =<< runAppM c (tx $ execute s)
7575

7676
info "Create table if not exists" $ createTable @Person True
7777
info "Delete all" $ deleteAll @Person -- Clear table before tutorial.
7878

79-
info "Insert one" $ returning $ insertOne $ john
79+
info "Insert one" $ insertOne john
8080

8181
info "Insert many" $
82-
insertMany [Person 25 "Alice", Person 25 "Bob"]
82+
insertMany [Person 25 "Alice" True, Person 25 "Bob" False]
8383

8484
info "Insert many, returning" $
85-
returning $ insertMany [Person 26 "Charlie", Person 26 "Dee"]
85+
returning $ insertMany [Person 26 "Charlie" False, Person 26 "Dee" True]
8686

8787
info "Insert many, returning age" $
88-
insertMany [Person 27 "Enid", Person 27 "Flavio"] ==> field @"age"
88+
insertMany [Person 27 "Enid" False, Person 27 "Flavio" True] ==> field @"age"
8989

9090
info "Select all" $ selectAll @Person
9191

0 commit comments

Comments
 (0)