Skip to content

Commit 0d8c2a1

Browse files
authored
Merge pull request #13 from anttih/psc-0.9-updates
Psc 0.9 updates
2 parents e388cd9 + fa1d8aa commit 0d8c2a1

File tree

6 files changed

+148
-90
lines changed

6 files changed

+148
-90
lines changed

bower.json

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,19 @@
2626
"output"
2727
],
2828
"dependencies": {
29-
"purescript-arrays": "~0.4.0",
30-
"purescript-either": "~0.2.0",
31-
"purescript-foreign": "~0.7.0",
32-
"purescript-foldable-traversable": "~0.4.0",
33-
"purescript-transformers": "~0.8.0",
34-
"purescript-aff": "~0.16.0",
35-
"purescript-integers": "~0.2.0"
29+
"purescript-arrays": "^1.0.0",
30+
"purescript-either": "^1.0.0",
31+
"purescript-foreign": "^1.0.0",
32+
"purescript-foldable-traversable": "^1.0.0",
33+
"purescript-transformers": "^1.0.0",
34+
"purescript-aff": "^1.0.0",
35+
"purescript-integers": "^1.0.0",
36+
"purescript-datetime": "^1.0.0",
37+
"purescript-unsafe-coerce": "^1.0.0",
38+
"purescript-nullable": "^1.0.1"
39+
},
40+
"devDependencies": {
41+
"purescript-spec": "~0.8.0",
42+
"purescript-generics": "~1.0.0"
3643
}
3744
}

schema.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ CREATE TABLE artist (
44
name text NOT NULL,
55
year int NOT NULL
66
);
7+
8+
CREATE table types (timestamp_no_tz timestamp without time zone);

src/Database/Postgres.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module Database.Postgres
1919
import Prelude
2020
import Control.Monad.Eff (Eff)
2121
import Data.Either (either)
22-
import Data.Function (Fn2(), runFn2)
22+
import Data.Function.Uncurried (Fn2(), runFn2)
2323
import Data.Array ((!!))
2424
import Data.Foreign (Foreign, ForeignError)
2525
import Data.Foreign.Class (class IsForeign, read)

src/Database/Postgres/SqlValue.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/Database/Postgres/SqlValue.purs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,44 @@ module Database.Postgres.SqlValue
44
, toSql
55
) where
66

7-
import Prelude ((<<<))
7+
import Prelude
8+
import Data.Enum (fromEnum)
89
import Data.Int (toNumber)
9-
import Data.Maybe (Maybe(..))
10+
import Data.Maybe (Maybe)
11+
import Data.Date (year, month, day)
12+
import Data.DateTime (DateTime(DateTime))
13+
import Data.Time (hour, minute, second)
14+
import Unsafe.Coerce (unsafeCoerce)
15+
import Data.Nullable (toNullable)
1016

1117
foreign import data SqlValue :: *
1218

1319
class IsSqlValue a where
1420
toSql :: a -> SqlValue
1521

1622
instance isSqlValueString :: IsSqlValue String where
17-
toSql = unsafeToSqlValue
23+
toSql = unsafeCoerce
1824

1925
instance isSqlValueNumber :: IsSqlValue Number where
20-
toSql = unsafeToSqlValue
26+
toSql = unsafeCoerce
2127

2228
instance isSqlValueInt :: IsSqlValue Int where
23-
toSql = unsafeToSqlValue <<< toNumber
29+
toSql = unsafeCoerce <<< toNumber
2430

2531
instance isSqlValueMaybe :: (IsSqlValue a) => IsSqlValue (Maybe a) where
26-
toSql Nothing = nullSqlValue
27-
toSql (Just x) = toSql x
28-
29-
foreign import unsafeToSqlValue :: forall a. a -> SqlValue
30-
31-
foreign import nullSqlValue :: SqlValue
32+
toSql = unsafeCoerce <<< toNullable <<< (toSql <$> _)
33+
34+
instance isSqlValueDateTime :: IsSqlValue DateTime where
35+
toSql = toSql <<< format
36+
where
37+
format (DateTime d t)
38+
= show (fromEnum (year d)) <> "-"
39+
<> zeroPad (fromEnum (month d)) <> "-"
40+
<> zeroPad (fromEnum (day d)) <> " "
41+
<> zeroPad (fromEnum (hour t)) <> ":"
42+
<> zeroPad (fromEnum (minute t)) <> ":"
43+
<> zeroPad (fromEnum (second t))
44+
45+
zeroPad :: Int -> String
46+
zeroPad i | i < 10 = "0" <> (show i)
47+
zeroPad i = show i

test/Main.purs

Lines changed: 104 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,42 @@
11
module Test.Main where
22

3-
import Control.Monad.Eff.Console as C
4-
import Control.Monad.Aff (Aff, apathize, attempt, runAff)
5-
import Control.Monad.Aff.Console (log, print)
3+
import Prelude
4+
import Control.Monad.Aff (Aff, apathize, attempt)
65
import Control.Monad.Eff (Eff)
76
import Control.Monad.Eff.Class (liftEff)
87
import Control.Monad.Eff.Console (CONSOLE)
98
import Control.Monad.Eff.Exception (error)
109
import Control.Monad.Error.Class (throwError)
10+
import Data.Array (length)
11+
import Data.Date (canonicalDate)
12+
import Data.DateTime (DateTime(..))
13+
import Data.Date.Component (Month(..))
14+
import Data.Enum (toEnum)
15+
import Data.Time (Time(..))
1116
import Data.Either (either)
12-
import Data.Foldable (foldMap)
17+
import Data.Foreign (Foreign)
18+
import Data.JSDate (toDateTime)
19+
import Data.Maybe (Maybe(Nothing, Just), maybe)
1320
import Data.Foreign.Class (class IsForeign, readProp)
14-
import Data.Maybe (Maybe)
15-
import Database.Postgres (DB, Query(Query), queryOne_, execute_, withConnection, query, withClient, end, query_, connect, queryValue_, disconnect, mkConnectionString)
21+
import Data.Generic (class Generic, gEq)
22+
import Database.Postgres (DB, Query(Query), queryOne_, execute, execute_, withConnection, query, withClient, end, query_, connect, queryValue_, mkConnectionString)
1623
import Database.Postgres.SqlValue (toSql)
1724
import Database.Postgres.Transaction (withTransaction)
18-
import Prelude (class Show, Unit, return, ($), bind, show, (<>), void, flip, (>>>), const)
25+
import Node.Process (PROCESS)
1926

20-
main :: forall eff. Eff ( console :: CONSOLE , db :: DB | eff ) Unit
21-
main = runAff C.print (const $ C.log "All ok") $ do
22-
print $ "connecting to " <> mkConnectionString connectionInfo <> "..."
23-
exampleUsingWithConnection
24-
exampleLowLevel
27+
import Unsafe.Coerce (unsafeCoerce)
2528

26-
res <- attempt exampleError
27-
either (const $ log "got an error, like we should") (const $ log "FAIL") res
28-
29-
exampleQueries
30-
31-
exampleTransaction
32-
33-
liftEff $ disconnect
29+
import Test.Spec (describe, it)
30+
import Test.Spec.Runner (run)
31+
import Test.Spec.Assertions (fail, shouldEqual)
32+
import Test.Spec.Reporter.Console (consoleReporter)
3433

3534
data Artist = Artist
3635
{ name :: String
3736
, year :: Int
3837
}
3938

39+
connectionInfo :: { host :: String, db :: String, port :: Int, user :: String, password :: String }
4040
connectionInfo =
4141
{ host: "localhost"
4242
, db: "test"
@@ -45,59 +45,101 @@ connectionInfo =
4545
, password: "test"
4646
}
4747

48-
exampleUsingWithConnection :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
49-
exampleUsingWithConnection = withConnection connectionInfo $ \c -> do
50-
execute_ (Query "delete from artist") c
51-
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
52-
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
53-
year <- queryValue_ (Query "insert into artist values ('Fairport Convention', 1967) returning year" :: Query Number) c
54-
print (show year)
55-
artists <- query_ (Query "select * from artist" :: Query Artist) c
56-
printRows artists
57-
58-
exampleLowLevel :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
59-
exampleLowLevel = do
60-
client <- connect connectionInfo
61-
artists <- query_ (Query "select * from artist order by name desc" :: Query Artist) client
62-
printRows artists
63-
liftEff $ end client
48+
main :: Eff (process :: PROCESS, console :: CONSOLE , db :: DB) Unit
49+
main = run [consoleReporter] do
50+
describe "connection string" do
51+
it "should build one from the connection record" do
52+
mkConnectionString connectionInfo `shouldEqual` "postgres://testuser:test@localhost:5432/test"
53+
54+
describe "withConnection" do
55+
it "Returns a connection" do
56+
withConnection connectionInfo $ \c -> do
57+
execute_ (Query "delete from artist") c
58+
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
59+
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
60+
let
61+
q :: Query Int
62+
q = Query "insert into artist values ('Fairport Convention', 1967) returning year"
63+
64+
year <- queryValue_ q c
65+
year `shouldEqual` (Just 1967)
66+
67+
artists <- query_ (Query "select * from artist" :: Query Artist) c
68+
length artists `shouldEqual` 3
69+
70+
describe "Low level API" do
71+
it "Can be used to manage connections manually" do
72+
client <- connect connectionInfo
73+
execute_ (Query "delete from artist") client
74+
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") client
75+
76+
artists <- query_ (Query "select * from artist order by name desc" :: Query Artist) client
77+
artists `shouldEqual` [Artist { name: "Led Zeppelin", year: 1968 }]
78+
79+
liftEff $ end client
80+
81+
describe "Error handling" do
82+
it "When query cannot be converted to the requested data type we get an error" do
83+
res <- attempt exampleError
84+
either (const $ pure unit) (const $ fail "FAIL") res
85+
86+
describe "Query params" do
87+
it "Select using a query param" do
88+
withClient connectionInfo $ \c -> do
89+
execute_ (Query "delete from artist") c
90+
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
91+
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
92+
execute_ (Query "insert into artist values ('Toto', 1977)") c
93+
artists <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "Toto"] c
94+
length artists `shouldEqual` 1
95+
96+
noRows <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "FAIL"] c
97+
length noRows `shouldEqual` 0
98+
99+
describe "data types" do
100+
it "datetimes can be inserted" do
101+
withConnection connectionInfo \c -> do
102+
execute_ (Query "delete from types") c
103+
let date = canonicalDate <$> toEnum 2016 <*> Just January <*> toEnum 25
104+
time = Time <$> toEnum 23 <*> toEnum 1 <*> toEnum 59 <*> toEnum 0
105+
dt = DateTime <$> date <*> time
106+
maybe (fail "Not a datetime") (\ts -> do
107+
execute (Query "insert into types(timestamp_no_tz) VALUES ($1)") [toSql ts] c
108+
ts' <- queryValue_ (Query "select timestamp_no_tz at time zone 'UTC' from types" :: Query Foreign) c
109+
let res = unsafeCoerce <$> ts' >>= toDateTime
110+
res `shouldEqual` (Just ts)
111+
) dt
112+
113+
114+
describe "transactions" do
115+
it "does not commit after an error inside a transation" do
116+
withConnection connectionInfo $ \c -> do
117+
execute_ (Query "delete from artist") c
118+
apathize $ tryInsert c
119+
one <- queryOne_ (Query "select * from artist" :: Query Artist) c
120+
121+
one `shouldEqual` Nothing
122+
where
123+
tryInsert = withTransaction $ \c -> do
124+
execute_ (Query "insert into artist values ('Not there', 1999)") c
125+
throwError $ error "fail"
64126

65127
exampleError :: forall eff. Aff (db :: DB | eff) (Maybe Artist)
66128
exampleError = withConnection connectionInfo $ \c -> do
67129
execute_ (Query "delete from artist") c
68130
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
69131
queryOne_ (Query "select year from artist") c
70132

71-
exampleQueries :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
72-
exampleQueries = withClient connectionInfo $ \c -> do
73-
log "Example queries with params:"
74-
execute_ (Query "delete from artist") c
75-
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
76-
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
77-
execute_ (Query "insert into artist values ('Toto', 1977)") c
78-
artists <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "Toto"] c
79-
printRows artists
80-
81-
exampleTransaction :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
82-
exampleTransaction = withConnection connectionInfo $ \c -> do
83-
execute_ (Query "delete from artist") c
84-
apathize $ tryInsert c
85-
one <- queryOne_ (Query "select * from artist" :: Query Artist) c
86-
void $ print one
87-
where
88-
tryInsert = withTransaction $ \c -> do
89-
execute_ (Query "insert into artist values ('Not there', 1999)") c
90-
throwError $ error "fail"
91-
92-
printRows :: forall a eff. (Show a) => Array a -> Aff (console :: C.CONSOLE | eff) Unit
93-
printRows rows = void $ log $ "result:\n" <> foldMap stringify rows
94-
where stringify = show >>> flip (<>) "\n"
95-
96133
instance artistShow :: Show Artist where
97134
show (Artist p) = "Artist (" <> p.name <> ", " <> show p.year <> ")"
98135

136+
derive instance genericArtist :: Generic Artist
137+
138+
instance eqArtist :: Eq Artist where
139+
eq = gEq
140+
99141
instance artistIsForeign :: IsForeign Artist where
100142
read obj = do
101143
n <- readProp "name" obj
102144
y <- readProp "year" obj
103-
return $ Artist { name: n, year: y }
145+
pure $ Artist { name: n, year: y }

0 commit comments

Comments
 (0)