Handle date with parsing on PS side

This commit is contained in:
Tomasz Rybarczyk 2018-04-22 18:15:43 +02:00
parent 037486a7ea
commit ed2ce9e592
3 changed files with 89 additions and 14 deletions

View File

@ -1,5 +1,12 @@
'use strict'; 'use strict';
// pg does strange thing converting DATE
// value to js Date, so we have
// to prevent this craziness
var pg = require('pg');
var DATE_OID = 1082;
pg.types.setTypeParser(DATE_OID, function(dateString) { return dateString; });
exports['null'] = null; exports['null'] = null;
exports.instantToString = function(i) { exports.instantToString = function(i) {

View File

@ -8,14 +8,18 @@ import Control.Monad.Except (runExcept)
import Data.Array as Array import Data.Array as Array
import Data.Bifunctor (lmap) import Data.Bifunctor (lmap)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.Date (Date, canonicalDate, day, month, year)
import Data.DateTime.Instant (Instant, instant) import Data.DateTime.Instant (Instant, instant)
import Data.Decimal (Decimal) import Data.Decimal (Decimal)
import Data.Decimal as Decimal import Data.Decimal as Decimal
import Data.Either (Either(..), note) import Data.Either (Either(..), note)
import Data.Enum (fromEnum, toEnum)
import Data.Foreign (Foreign, isNull, readArray, readBoolean, readChar, readInt, readNumber, readString, toForeign, unsafeFromForeign) import Data.Foreign (Foreign, isNull, readArray, readBoolean, readChar, readInt, readNumber, readString, toForeign, unsafeFromForeign)
import Data.Int (fromString)
import Data.List (List) import Data.List (List)
import Data.List as List import Data.List as List
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.String (Pattern(..), split)
import Data.Time.Duration (Milliseconds(..)) import Data.Time.Duration (Milliseconds(..))
import Data.Traversable (traverse) import Data.Traversable (traverse)
@ -82,9 +86,33 @@ instance toSQLValueInstant :: ToSQLValue Instant where
instance fromSQLValueInstant :: FromSQLValue Instant where instance fromSQLValueInstant :: FromSQLValue Instant where
fromSQLValue v = do fromSQLValue v = do
t instantFromString Left Right v t <- instantFromString Left Right v
note ("Instant construction failed for given timestamp: " <> show t) $ instant (Milliseconds t) note ("Instant construction failed for given timestamp: " <> show t) $ instant (Milliseconds t)
instance toSQLValueDate :: ToSQLValue Date where
toSQLValue date =
let
y = fromEnum $ year date
m = fromEnum $ month date
d = fromEnum $ day date
in
toForeign $ show y <> "-" <> show m <> "-" <> show d
instance fromSQLValueDate :: FromSQLValue Date where
fromSQLValue v = do
s <- lmap show $ runExcept (readString v)
let
msg = "Date parsing failed for value: " <> s
case split (Pattern "-") s of
[y, m, d] -> do
let
result = canonicalDate
<$> (toEnum =<< fromString y)
<*> (toEnum =<< fromString m)
<*> (toEnum =<< fromString d)
note msg result
_ -> Left msg
instance toSQLValueMaybe :: (ToSQLValue a) => ToSQLValue (Maybe a) where instance toSQLValueMaybe :: (ToSQLValue a) => ToSQLValue (Maybe a) where
toSQLValue Nothing = null toSQLValue Nothing = null
toSQLValue (Just x) = toSQLValue x toSQLValue (Just x) = toSQLValue x
@ -104,10 +132,10 @@ instance toSQLValueDecimal :: ToSQLValue Decimal where
instance fromSQLValueDecimal :: FromSQLValue Decimal where instance fromSQLValueDecimal :: FromSQLValue Decimal where
fromSQLValue v = do fromSQLValue v = do
s lmap show $ runExcept (readString v) s <- lmap show $ runExcept (readString v)
note ("Decimal literal parsing failed: " <> s) (Decimal.fromString s) note ("Decimal literal parsing failed: " <> s) (Decimal.fromString s)
foreign import null :: Foreign foreign import null :: Foreign
foreign import instantToString :: Instant -> Foreign foreign import instantToString :: Instant -> Foreign
foreign import instantFromString :: (String Either String Number) (Number Either String Number) Foreign Either String Number foreign import instantFromString :: (String -> Either String Number) -> (Number -> Either String Number) -> Foreign -> Either String Number
foreign import unsafeIsBuffer :: a. a -> Boolean foreign import unsafeIsBuffer :: a. a -> Boolean

View File

@ -12,19 +12,24 @@ import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Exception (EXCEPTION, error) import Control.Monad.Eff.Exception (EXCEPTION, error)
import Control.Monad.Eff.Now (NOW) import Control.Monad.Eff.Now (NOW)
import Control.Monad.Error.Class (catchError, throwError, try) import Control.Monad.Error.Class (catchError, throwError, try)
import Data.Array (zip)
import Data.Date (Date, canonicalDate)
import Data.DateTime.Instant (Instant, unInstant) import Data.DateTime.Instant (Instant, unInstant)
import Data.Decimal as D import Data.Decimal as D
import Data.Foldable (all) import Data.Enum (toEnum)
import Data.Foldable (all, length)
import Data.JSDate (toInstant) import Data.JSDate (toInstant)
import Data.JSDate as JSDate import Data.JSDate as JSDate
import Data.Maybe (Maybe(..), fromJust) import Data.Maybe (Maybe(..), fromJust)
import Data.Newtype (unwrap) import Data.Newtype (unwrap)
import Data.Tuple (Tuple(..))
import Database.PostgreSQL (Connection, POSTGRESQL, PoolConfiguration, Query(Query), Row0(Row0), Row1(Row1), Row2(Row2), Row3(Row3), Row9(Row9), execute, newPool, query, scalar, withConnection, withTransaction) import Database.PostgreSQL (Connection, POSTGRESQL, PoolConfiguration, Query(Query), Row0(Row0), Row1(Row1), Row2(Row2), Row3(Row3), Row9(Row9), execute, newPool, query, scalar, withConnection, withTransaction)
import Math ((%)) import Math ((%))
import Partial.Unsafe (unsafePartial) import Partial.Unsafe (unsafePartial)
import Test.Assert (ASSERT, assert) import Test.Assert (ASSERT, assert)
import Test.Unit (suite) import Test.Unit (suite)
import Test.Unit as Test.Unit import Test.Unit as Test.Unit
import Test.Unit.Assert (equal)
import Test.Unit.Console (TESTOUTPUT) import Test.Unit.Console (TESTOUTPUT)
import Test.Unit.Main (runTest) import Test.Unit.Main (runTest)
@ -50,18 +55,32 @@ test conn t a = Test.Unit.test t (withRollback conn a)
now :: eff. Eff (now :: NOW | eff) Instant now :: eff. Eff (now :: NOW | eff) Instant
now = unsafePartial $ (fromJust <<< toInstant) <$> JSDate.now now = unsafePartial $ (fromJust <<< toInstant) <$> JSDate.now
main :: eff. Eff (assert :: ASSERT, avar :: AVAR, console :: CONSOLE, exception :: EXCEPTION, now :: NOW, postgreSQL :: POSTGRESQL, testOutput :: TESTOUTPUT | eff) Unit main
:: eff
. Eff
( assert :: ASSERT
, avar :: AVAR
, console :: CONSOLE
, exception :: EXCEPTION
, now :: NOW
, postgreSQL :: POSTGRESQL
, testOutput :: TESTOUTPUT | eff
)
Unit
main = void $ launchAff do main = void $ launchAff do
pool <- newPool config pool <- newPool config
withConnection pool \conn -> do withConnection pool \conn -> do
execute conn (Query """ execute conn (Query """
CREATE TEMPORARY TABLE foods ( CREATE TEMPORARY TABLE foods (
name text NOT NULL, name text NOT NULL,
delicious boolean NOT NULL, delicious boolean NOT NULL,
price NUMERIC(4,2) NOT NULL, price NUMERIC(4,2) NOT NULL,
added TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, added TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (name) PRIMARY KEY (name)
) );
CREATE TEMPORARY TABLE dates (
date date NOT NULL
);
""") Row0 """) Row0
liftEff $ runTest $ do liftEff $ runTest $ do
@ -115,7 +134,7 @@ main = void $ launchAff do
""") Row0 """) Row0
liftEff <<< assert $ names == [Row2 "pork" true, Row2 "rookworst" true] liftEff <<< assert $ names == [Row2 "pork" true, Row2 "rookworst" true]
test conn "select default instant value" $ do test conn "handling instant value" $ do
before <- liftEff $ (unwrap <<< unInstant) <$> now before <- liftEff $ (unwrap <<< unInstant) <$> now
insertFood insertFood
added <- query conn (Query """ added <- query conn (Query """
@ -131,7 +150,7 @@ main = void $ launchAff do
&& after >= (unwrap $ unInstant t)) && after >= (unwrap $ unInstant t))
added added
test conn "select decimal" $ do test conn "handling decimal value" $ do
insertFood insertFood
sauerkrautPrice <- query conn (Query """ sauerkrautPrice <- query conn (Query """
SELECT price SELECT price
@ -140,6 +159,27 @@ main = void $ launchAff do
""") Row0 """) Row0
liftEff <<< assert $ sauerkrautPrice == [Row1 (D.fromString "3.30")] liftEff <<< assert $ sauerkrautPrice == [Row1 (D.fromString "3.30")]
test conn "handling date value" $ do
let
date y m d =
canonicalDate <$> toEnum y <*> toEnum m <*> toEnum d
d1 = unsafePartial $ fromJust $ date 2010 2 31
d2 = unsafePartial $ fromJust $ date 2017 2 1
d3 = unsafePartial $ fromJust $ date 2020 6 31
execute conn (Query """
INSERT INTO dates (date)
VALUES ($1), ($2), ($3)
""") (Row3 d1 d2 d3)
(dates :: Array (Row1 Date)) <- query conn (Query """
SELECT *
FROM dates
ORDER BY date ASC
""") Row0
equal 3 (length dates)
liftEff <<< assert $ all (\(Tuple (Row1 r) e) -> e == r) $ (zip dates [d1, d2, d3])
config :: PoolConfiguration config :: PoolConfiguration
config = config =