diff --git a/spago.yaml b/spago.yaml index 177f229..b717733 100644 --- a/spago.yaml +++ b/spago.yaml @@ -20,6 +20,7 @@ package: - "foreign-object" - "identity" - "integers" + - js-bigints - "js-date" - "lists" - "maybe" diff --git a/src/Database/PostgreSQL/Value.purs b/src/Database/PostgreSQL/Value.purs index dda7f4a..d6eb880 100644 --- a/src/Database/PostgreSQL/Value.purs +++ b/src/Database/PostgreSQL/Value.purs @@ -6,11 +6,10 @@ module Database.PostgreSQL.Value where import Prelude -import Control.Monad.Error.Class (throwError) +import Control.Monad.Error.Class (liftMaybe, throwError) import Control.Monad.Except (ExceptT, except, runExcept, runExceptT) import Data.Argonaut (Json) import Data.Argonaut (stringify) as Argonaut -import Data.Foldable (foldl) import Data.Array as Array import Data.Bifunctor (lmap) import Data.ByteString (ByteString) @@ -20,6 +19,7 @@ import Data.Decimal (Decimal) import Data.Decimal as Decimal import Data.Either (Either(..), note) import Data.Enum (fromEnum, toEnum) +import Data.Foldable (foldl) import Data.Identity (Identity) import Data.Int (fromString) import Data.JSDate (JSDate) @@ -34,6 +34,8 @@ import Data.Traversable (sequence, traverse) import Foreign (Foreign, ForeignError(..), MultipleErrors, isNull, readArray, readBoolean, readChar, readInt, readNumber, readString, renderForeignError, unsafeFromForeign, unsafeToForeign) import Foreign.Generic.Internal (readObject) import Foreign.Object (Object) +import JS.BigInt (BigInt) +import JS.BigInt as BigInt -- | Convert things to SQL values. class ToSQLValue a where @@ -49,6 +51,13 @@ instance fromSQLValueBoolean :: FromSQLValue Boolean where else instance fromSQLValueChar :: FromSQLValue Char where fromSQLValue = lmap show <<< runExcept <<< readChar +else instance fromSQLValueBigInt :: FromSQLValue BigInt where + fromSQLValue = + lmap show + <<< runExcept + <<< flip bind (\s -> liftMaybe (pure $ ForeignError $ "invalid bigint: " <> s) $ BigInt.fromString s) + <<< readString + else instance fromSQLValueInt :: FromSQLValue Int where fromSQLValue = lmap show <<< runExcept <<< readInt @@ -130,6 +139,9 @@ instance toSQLValueBoolean :: ToSQLValue Boolean where else instance toSQLValueChar :: ToSQLValue Char where toSQLValue = unsafeToForeign +else instance toSQLValueBigInt :: ToSQLValue BigInt where + toSQLValue = unsafeToForeign <<< BigInt.toString + else instance toSQLValueInt :: ToSQLValue Int where toSQLValue = unsafeToForeign diff --git a/test/Main.purs b/test/Main.purs index 7a8063d..55bd755 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -23,7 +23,7 @@ import Data.Newtype (unwrap) import Data.Number ((%)) import Data.Tuple (Tuple(..)) import Data.Tuple.Nested ((/\)) -import Database.PostgreSQL (Client, Configuration, Connection(..), PGConnectionURI, PGError(..), Pool, Query(Query), Row0(Row0), Row1(Row1), Row2(Row2), Row3(Row3), Row9(Row9), fromClient, fromPool, parseURI) +import Database.PostgreSQL (Client, Configuration, Connection(..), PGConnectionURI, PGError(..), Pool, Query(Query), Row0(Row0), Row1(Row1), Row12(..), Row2(Row2), Row3(Row3), Row9(Row9), fromClient, fromPool, parseURI) import Database.PostgreSQL.PG (command, execute, onIntegrityError, query, scalar) import Database.PostgreSQL.PG (withClient, withClientTransaction) as PG import Database.PostgreSQL.Pool (new) as Pool @@ -34,6 +34,8 @@ import Effect.Class (liftEffect) import Effect.Exception (message) import Foreign.Object (Object) import Foreign.Object (fromFoldable) as Object +import JS.BigInt (BigInt) +import JS.BigInt as BigInt import JS.Unsafe.Stringify (unsafeStringify) import Partial.Unsafe (unsafePartial) import Test.Assert (assert) @@ -45,6 +47,9 @@ import Test.Unit as Test.Unit import Test.Unit.Assert (equal) import Test.Unit.Main (runTest) +bigintFromString :: String -> BigInt +bigintFromString = unsafePartial fromJust <<< BigInt.fromString + withClient :: forall a. Pool -> (Client -> AppM a) -> AppM a withClient = PG.withClient runExceptT @@ -124,6 +129,7 @@ main = do delicious boolean NOT NULL, price NUMERIC(4,2) NOT NULL, added TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + huge_number INT8 NULL, PRIMARY KEY (name) ); CREATE TEMPORARY TABLE dates ( @@ -263,20 +269,23 @@ main = do execute handle ( Query """ - INSERT INTO foods (name, delicious, price) - VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9) + INSERT INTO foods (name, delicious, price, huge_number) + VALUES ($1, $2, $3, $4), ($5, $6, $7, $8), ($9, $10, $11, $12) """ ) - ( Row9 + ( Row12 "pork" true (D.fromString "8.30") + (bigintFromString "123") "sauerkraut" false (D.fromString "3.30") + (bigintFromString "456") "rookworst" true (D.fromString "5.60") + (bigintFromString "789") ) test handle "select column subset" $ do @@ -358,6 +367,20 @@ main = do ) Row0 liftEffect <<< assert $ sauerkrautPrice == [ Row1 (D.fromString "3.30") ] + test handle "handling bigint value" + $ do + insertFood + hugeNumber <- + query handle + ( Query + """ + SELECT huge_number + FROM foods + WHERE NOT delicious + """ + ) + Row0 + liftEffect <<< assert $ hugeNumber == [ Row1 (bigintFromString "456") ] transactionTest "integrity error handling" $ do withRollback client do