diff --git a/bower.json b/bower.json index ac1de63..b239b93 100644 --- a/bower.json +++ b/bower.json @@ -26,20 +26,22 @@ "output" ], "dependencies": { - "purescript-arrays": "^3.0.0", - "purescript-either": "^2.0.0", - "purescript-foreign": "^3.0.0", - "purescript-foldable-traversable": "^2.0.0", - "purescript-transformers": "^2.0.0", - "purescript-aff": "^2.0.0", - "purescript-integers": "^2.0.0", - "purescript-datetime": "^2.0.0", - "purescript-unsafe-coerce": "^2.0.0", - "purescript-nullable": "^2.0.0" + "purescript-arrays": "^4.0.0", + "purescript-either": "^3.0.0", + "purescript-foreign": "^4.0.0", + "purescript-foldable-traversable": "^3.0.0", + "purescript-transformers": "^3.0.0", + "purescript-aff": "^3.0.0", + "purescript-integers": "^3.0.0", + "purescript-datetime": "^3.0.0", + "purescript-unsafe-coerce": "^3.0.0", + "purescript-nullable": "^3.0.0", + "purescript-prelude": "^3.0.0", + "purescript-foreign-generic": "^4.0.0" }, "devDependencies": { - "purescript-spec": "^0.10.0", - "purescript-generics": "^3.0.0", - "purescript-js-date": "^3.0.0" + "purescript-spec": "^1.0.0", + "purescript-generics": "^4.0.0", + "purescript-js-date": "^4.0.0" } } diff --git a/src/Database/Postgres.purs b/src/Database/Postgres.purs index 84a6560..b6642d0 100644 --- a/src/Database/Postgres.purs +++ b/src/Database/Postgres.purs @@ -17,12 +17,12 @@ module Database.Postgres ) where import Prelude -import Control.Monad.Eff (Eff) +import Control.Monad.Eff (kind Effect, Eff) import Data.Either (Either, either) import Data.Function.Uncurried (Fn2(), runFn2) import Data.Array ((!!)) import Data.Foreign (Foreign, MultipleErrors) -import Data.Foreign.Class (class IsForeign, read) +import Data.Foreign.Class (class Decode, decode) import Data.Maybe (Maybe(Just, Nothing), maybe) import Control.Monad.Except (runExcept) import Control.Monad.Aff (Aff, finally) @@ -35,9 +35,9 @@ import Database.Postgres.SqlValue (SqlValue) newtype Query a = Query String -foreign import data Client :: * +foreign import data Client :: Type -foreign import data DB :: ! +foreign import data DB :: Effect type ConnectionString = String @@ -72,45 +72,45 @@ execute_ (Query sql) client = void $ runQuery_ sql client -- | Runs a query and returns all results. query :: forall eff a - . (IsForeign a) + . (Decode a) => Query a -> Array SqlValue -> Client -> Aff (db :: DB | eff) (Array a) query (Query sql) params client = do rows <- runQuery sql params client - either liftError pure (runExcept (sequence $ read <$> rows)) + either liftError pure (runExcept (sequence $ decode <$> rows)) -- | Just like `query` but does not make any param replacement -query_ :: forall eff a. (IsForeign a) => Query a -> Client -> Aff (db :: DB | eff) (Array a) +query_ :: forall eff a. (Decode a) => Query a -> Client -> Aff (db :: DB | eff) (Array a) query_ (Query sql) client = do rows <- runQuery_ sql client - either liftError pure (runExcept (sequence $ read <$> rows)) + either liftError pure (runExcept (sequence $ decode <$> rows)) -- | Runs a query and returns the first row, if any queryOne :: forall eff a - . (IsForeign a) + . (Decode a) => Query a -> Array SqlValue -> Client -> Aff (db :: DB | eff) (Maybe a) queryOne (Query sql) params client = do rows <- runQuery sql params client - maybe (pure Nothing) (either liftError (pure <<< Just)) (readFirst rows) + maybe (pure Nothing) (either liftError (pure <<< Just)) (decodeFirst rows) -- | Just like `queryOne` but does not make any param replacement -queryOne_ :: forall eff a. (IsForeign a) => Query a -> Client -> Aff (db :: DB | eff) (Maybe a) +queryOne_ :: forall eff a. (Decode a) => Query a -> Client -> Aff (db :: DB | eff) (Maybe a) queryOne_ (Query sql) client = do rows <- runQuery_ sql client - maybe (pure Nothing) (either liftError (pure <<< Just)) (readFirst rows) + maybe (pure Nothing) (either liftError (pure <<< Just)) (decodeFirst rows) -- | Runs a query and returns a single value, if any. queryValue :: forall eff a - . (IsForeign a) + . (Decode a) => Query a -> Array SqlValue -> Client -> Aff (db :: DB | eff) (Maybe a) queryValue (Query sql) params client = do val <- runQueryValue sql params client - pure $ either (const Nothing) Just (runExcept (read val)) + pure $ either (const Nothing) Just (runExcept (decode val)) -- | Just like `queryValue` but does not make any param replacement -queryValue_ :: forall eff a. (IsForeign a) => Query a -> Client -> Aff (db :: DB | eff) (Maybe a) +queryValue_ :: forall eff a. (Decode a) => Query a -> Client -> Aff (db :: DB | eff) (Maybe a) queryValue_ (Query sql) client = do val <- runQueryValue_ sql client - either liftError (pure <<< Just) $ runExcept (read val) + either liftError (pure <<< Just) $ runExcept (decode val) -- | Connects to the database, calls the provided function with the client -- | and returns the results. @@ -130,8 +130,8 @@ withClient :: forall eff a -> Aff (db :: DB | eff) a withClient info p = runFn2 _withClient (mkConnectionString info) p -readFirst :: forall a. IsForeign a => Array Foreign -> Maybe (Either MultipleErrors a) -readFirst rows = runExcept <<< read <$> (rows !! 0) +decodeFirst :: forall a. Decode a => Array Foreign -> Maybe (Either MultipleErrors a) +decodeFirst rows = runExcept <<< decode <$> (rows !! 0) liftError :: forall e a. MultipleErrors -> Aff e a liftError errs = throwError $ error (show errs) diff --git a/src/Database/Postgres/SqlValue.purs b/src/Database/Postgres/SqlValue.purs index 301eda8..1292b68 100644 --- a/src/Database/Postgres/SqlValue.purs +++ b/src/Database/Postgres/SqlValue.purs @@ -14,7 +14,7 @@ import Data.Time (hour, minute, second) import Unsafe.Coerce (unsafeCoerce) import Data.Nullable (toNullable) -foreign import data SqlValue :: * +foreign import data SqlValue :: Type class IsSqlValue a where toSql :: a -> SqlValue diff --git a/test/Main.purs b/test/Main.purs index 4d3565a..89bbc6b 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -1,35 +1,37 @@ module Test.Main where import Prelude + import Control.Monad.Aff (Aff, apathize, attempt) +import Control.Monad.Aff.AVar (AVAR) import Control.Monad.Eff (Eff) import Control.Monad.Eff.Class (liftEff) import Control.Monad.Eff.Console (CONSOLE) import Control.Monad.Eff.Exception (error) +import Control.Monad.Eff.Timer (TIMER) import Control.Monad.Error.Class (throwError) import Data.Array (length) import Data.Date (canonicalDate) -import Data.DateTime (DateTime(..)) import Data.Date.Component (Month(..)) -import Data.Enum (toEnum) -import Data.Time (Time(..)) +import Data.DateTime (DateTime(..)) import Data.Either (either) +import Data.Enum (toEnum) import Data.Foreign (Foreign) +import Data.Foreign.Class (class Decode, decode) +import Data.Foreign.Index (readProp) +import Data.Generic (class Generic, gEq) import Data.JSDate (toDateTime) import Data.Maybe (Maybe(Nothing, Just), maybe) -import Data.Foreign.Class (class IsForeign, readProp) -import Data.Generic (class Generic, gEq) -import Database.Postgres (DB, Query(Query), queryOne_, execute, execute_, withConnection, query, withClient, end, query_, connect, queryValue_, mkConnectionString) +import Data.Time (Time(..)) +import Database.Postgres (DB, Query(Query), connect, end, execute, execute_, mkConnectionString, query, queryOne_, queryValue_, query_, withClient, withConnection) import Database.Postgres.SqlValue (toSql) import Database.Postgres.Transaction (withTransaction) import Node.Process (PROCESS) - -import Unsafe.Coerce (unsafeCoerce) - import Test.Spec (describe, it) -import Test.Spec.Runner (run) import Test.Spec.Assertions (fail, shouldEqual) import Test.Spec.Reporter.Console (consoleReporter) +import Test.Spec.Runner (run) +import Unsafe.Coerce (unsafeCoerce) data Artist = Artist { name :: String @@ -45,7 +47,16 @@ connectionInfo = , password: "test" } -main :: Eff (process :: PROCESS, console :: CONSOLE , db :: DB) Unit +main :: forall eff. + Eff + ( console :: CONSOLE + , timer :: TIMER + , avar :: AVAR + , process :: PROCESS + , db :: DB + | eff + ) + Unit main = run [consoleReporter] do describe "connection string" do it "should build one from the connection record" do @@ -111,7 +122,7 @@ main = run [consoleReporter] do ) dt - describe "transactions" do + describe "transactions" do it "does not commit after an error inside a transation" do withConnection connectionInfo $ \c -> do execute_ (Query "delete from artist") c @@ -138,8 +149,8 @@ derive instance genericArtist :: Generic Artist instance eqArtist :: Eq Artist where eq = gEq -instance artistIsForeign :: IsForeign Artist where - read obj = do - n <- readProp "name" obj - y <- readProp "year" obj +instance artistIsForeign :: Decode Artist where + decode obj = do + n <- decode =<< readProp "name" obj + y <- decode =<< readProp "year" obj pure $ Artist { name: n, year: y }