2024-03-27 02:59:28 +00:00
|
|
|
module Test.Data.Postgres where
|
|
|
|
|
|
|
|
import Prelude
|
|
|
|
|
2024-03-29 19:52:05 +00:00
|
|
|
import Control.Monad.Gen (chooseInt, elements, oneOf)
|
2024-03-31 01:38:52 +00:00
|
|
|
import Control.Parallel (parTraverse_)
|
|
|
|
import Data.Array (intercalate)
|
2024-03-29 19:52:05 +00:00
|
|
|
import Data.Array as Array
|
|
|
|
import Data.Array.NonEmpty as Array.NonEmpty
|
|
|
|
import Data.DateTime (DateTime(..), canonicalDate)
|
2024-03-27 02:59:28 +00:00
|
|
|
import Data.DateTime.Instant as Instant
|
2024-03-29 19:52:05 +00:00
|
|
|
import Data.Enum (toEnum)
|
|
|
|
import Data.Foldable (fold)
|
|
|
|
import Data.Identity (Identity)
|
2024-03-27 02:59:28 +00:00
|
|
|
import Data.Int as Int
|
2024-03-29 19:52:05 +00:00
|
|
|
import Data.Maybe (Maybe(..), fromJust, maybe)
|
|
|
|
import Data.Newtype (class Newtype, unwrap, wrap)
|
|
|
|
import Data.Number (abs) as Number
|
2024-04-02 20:58:34 +00:00
|
|
|
import Data.Postgres (class Rep)
|
2024-03-29 19:52:05 +00:00
|
|
|
import Data.Postgres.Query.Builder as Q
|
2024-04-02 20:58:34 +00:00
|
|
|
import Data.Postgres.Raw (Raw, jsNull)
|
2024-03-27 02:59:28 +00:00
|
|
|
import Data.Postgres.Raw as Raw
|
2024-03-31 00:49:54 +00:00
|
|
|
import Data.Postgres.Result (class FromRow)
|
2024-03-27 02:59:28 +00:00
|
|
|
import Data.RFC3339String as DateTime.ISO
|
2024-03-29 19:52:05 +00:00
|
|
|
import Data.String as String
|
|
|
|
import Data.Time (Time(..))
|
|
|
|
import Data.Traversable (for, sequence)
|
|
|
|
import Data.Tuple.Nested ((/\))
|
|
|
|
import Effect (Effect)
|
|
|
|
import Effect.Aff (Aff)
|
|
|
|
import Effect.Aff.Postgres.Client (exec, query)
|
2024-03-27 02:59:28 +00:00
|
|
|
import Effect.Class (liftEffect)
|
2024-03-29 19:52:05 +00:00
|
|
|
import Effect.Postgres.Client (Client)
|
2024-03-27 02:59:28 +00:00
|
|
|
import Effect.Unsafe (unsafePerformEffect)
|
2024-03-29 19:52:05 +00:00
|
|
|
import Foreign (Foreign, unsafeToForeign)
|
2024-03-27 17:20:33 +00:00
|
|
|
import Foreign.Object as Object
|
2024-03-29 19:52:05 +00:00
|
|
|
import JS.BigInt (BigInt)
|
|
|
|
import JS.BigInt as BigInt
|
|
|
|
import Node.Buffer (Buffer)
|
|
|
|
import Node.Buffer as Buffer
|
2024-03-27 02:59:28 +00:00
|
|
|
import Partial.Unsafe (unsafePartial)
|
2024-03-29 19:52:05 +00:00
|
|
|
import Simple.JSON (writeJSON)
|
2024-03-31 01:38:52 +00:00
|
|
|
import Test.Common (withClient, withPoolClient)
|
2024-03-29 19:52:05 +00:00
|
|
|
import Test.QuickCheck (class Arbitrary, arbitrary, randomSeed)
|
|
|
|
import Test.QuickCheck.Gen (sample, vectorOf)
|
2024-03-31 01:38:52 +00:00
|
|
|
import Test.Spec (Spec, SpecT, around, describe, it, parallel)
|
2024-03-29 19:52:05 +00:00
|
|
|
import Test.Spec.Assertions (fail)
|
|
|
|
|
|
|
|
foreign import readBigInt64BE :: Buffer -> Effect BigInt
|
|
|
|
foreign import dbg :: forall a. a -> Effect Unit
|
|
|
|
|
|
|
|
newtype GenSmallInt = GenSmallInt Int
|
2024-03-31 00:49:54 +00:00
|
|
|
|
2024-03-29 19:52:05 +00:00
|
|
|
derive instance Newtype GenSmallInt _
|
|
|
|
instance Arbitrary GenSmallInt where
|
|
|
|
arbitrary = wrap <$> chooseInt (-32768) 32767
|
|
|
|
|
|
|
|
newtype GenDateTime = GenDateTime DateTime
|
2024-03-31 00:49:54 +00:00
|
|
|
|
2024-03-29 19:52:05 +00:00
|
|
|
derive instance Newtype GenDateTime _
|
|
|
|
instance Arbitrary GenDateTime where
|
|
|
|
arbitrary = do
|
|
|
|
yr <- chooseInt 1970 2100
|
|
|
|
mo <- chooseInt 1 12
|
|
|
|
dy <- chooseInt 1 28
|
|
|
|
hr <- chooseInt 0 23
|
|
|
|
mn <- chooseInt 0 59
|
|
|
|
sc <- chooseInt 0 59
|
|
|
|
ms <- chooseInt 0 999
|
|
|
|
let
|
|
|
|
date = unsafePartial fromJust $ Just canonicalDate <*> toEnum yr <*> toEnum mo <*> toEnum dy
|
|
|
|
time = unsafePartial fromJust $ Just Time <*> toEnum hr <*> toEnum mn <*> toEnum sc <*> toEnum ms
|
|
|
|
pure $ wrap $ DateTime date time
|
|
|
|
|
|
|
|
newtype GenString = GenString String
|
2024-03-31 00:49:54 +00:00
|
|
|
|
2024-03-29 19:52:05 +00:00
|
|
|
derive instance Newtype GenString _
|
|
|
|
instance Arbitrary GenString where
|
|
|
|
arbitrary = do
|
|
|
|
let chars = unsafePartial fromJust $ Array.NonEmpty.fromArray $ String.split (wrap "") "abcdefghijklmnopqrstuvwxyz01234567890 _-=><|\\/"
|
|
|
|
len <- chooseInt 0 100
|
|
|
|
chars' <- vectorOf len (elements chars)
|
|
|
|
pure $ wrap $ fold chars'
|
|
|
|
|
|
|
|
newtype GenSmallFloat = GenSmallFloat Number
|
2024-03-31 00:49:54 +00:00
|
|
|
|
2024-03-29 19:52:05 +00:00
|
|
|
derive instance Newtype GenSmallFloat _
|
|
|
|
instance Arbitrary GenSmallFloat where
|
|
|
|
arbitrary = do
|
|
|
|
let byte = chooseInt 0 7
|
|
|
|
bytes <- sequence $ Array.replicate 4 byte
|
|
|
|
pure
|
|
|
|
$ wrap
|
|
|
|
$ unsafePerformEffect do
|
|
|
|
buf <- Buffer.fromArray bytes
|
|
|
|
Buffer.read Buffer.FloatBE 0 buf
|
|
|
|
|
|
|
|
newtype GenBigInt = GenBigInt BigInt
|
|
|
|
|
|
|
|
derive instance Newtype GenBigInt _
|
|
|
|
|
|
|
|
instance Arbitrary GenBigInt where
|
|
|
|
arbitrary = do
|
|
|
|
let byte = chooseInt 0 7
|
|
|
|
bytes <- sequence $ Array.replicate 8 byte
|
|
|
|
let
|
|
|
|
bigint = unsafePerformEffect do
|
|
|
|
buf <- Buffer.fromArray bytes
|
|
|
|
readBigInt64BE buf
|
|
|
|
pure $ wrap bigint
|
|
|
|
|
|
|
|
newtype GenJSON = GenJSON Foreign
|
|
|
|
|
|
|
|
derive instance Newtype GenJSON _
|
|
|
|
|
|
|
|
instance Arbitrary GenJSON where
|
|
|
|
arbitrary =
|
|
|
|
let
|
|
|
|
json _ = map wrap $ oneOf' [ prim, array unit, obj unit ]
|
|
|
|
oneOf' = oneOf <<< unsafePartial fromJust <<< Array.NonEmpty.fromArray
|
|
|
|
elements' = elements <<< unsafePartial fromJust <<< Array.NonEmpty.fromArray
|
|
|
|
prim = oneOf'
|
|
|
|
[ pure $ unsafeToForeign jsNull
|
|
|
|
, unsafeToForeign <$> arbitrary @Number
|
|
|
|
, unsafeToForeign <$> arbitrary @String
|
|
|
|
]
|
|
|
|
array _ = map unsafeToForeign $ vectorOf 3 prim
|
|
|
|
obj _ = do
|
|
|
|
keys <- vectorOf 3 (elements' [ "foo", "bar", "baz", "quux", "duck", "dog", "cat", "cow" ])
|
|
|
|
kvs <- for keys \k -> (k /\ _) <$> prim
|
|
|
|
pure $ unsafeToForeign $ Object.fromFoldable kvs
|
|
|
|
in
|
|
|
|
json unit
|
2024-03-27 02:59:28 +00:00
|
|
|
|
|
|
|
asRaw :: forall a. a -> Raw
|
|
|
|
asRaw = Raw.unsafeFromForeign <<< unsafeToForeign
|
|
|
|
|
2024-03-31 01:38:52 +00:00
|
|
|
type PursType = String
|
|
|
|
type SQLType = String
|
|
|
|
type FromArbitrary x a = x -> a
|
|
|
|
type IsEqual a = a -> a -> Boolean
|
|
|
|
|
|
|
|
class (Show a, FromRow a, Rep a) <= Checkable a
|
|
|
|
|
|
|
|
instance (Show a, FromRow a, Rep a) => Checkable a
|
|
|
|
|
2024-03-27 02:59:28 +00:00
|
|
|
spec :: Spec Unit
|
|
|
|
spec =
|
|
|
|
let
|
2024-03-31 01:38:52 +00:00
|
|
|
check
|
|
|
|
:: forall @a @x
|
|
|
|
. Checkable a
|
|
|
|
=> Arbitrary x
|
|
|
|
=> { purs :: String
|
|
|
|
, sql :: String
|
|
|
|
, fromArb :: x -> a
|
|
|
|
, isEq :: a -> a -> Boolean
|
|
|
|
}
|
|
|
|
-> SpecT Aff Client Identity Unit
|
|
|
|
check { purs, sql, fromArb, isEq } =
|
2024-03-29 19:52:05 +00:00
|
|
|
it (purs <> " <> " <> sql) \c -> do
|
|
|
|
let
|
2024-03-31 01:38:52 +00:00
|
|
|
tab =
|
|
|
|
String.replace (wrap " ") (wrap "_")
|
|
|
|
$ String.replace (wrap "[") (wrap "")
|
|
|
|
$ String.replace (wrap "]") (wrap "")
|
|
|
|
$ sql <> "_is_" <> String.toLower purs
|
|
|
|
createtab =
|
|
|
|
intercalate "\n"
|
|
|
|
[ "create temp table " <> tab
|
|
|
|
, " ( val " <> sql
|
|
|
|
, " );"
|
|
|
|
]
|
2024-03-29 19:52:05 +00:00
|
|
|
ser x =
|
|
|
|
Q.build do
|
2024-03-31 01:38:52 +00:00
|
|
|
x' <- Q.param $ fromArb x
|
|
|
|
let val = x' <> " :: " <> sql
|
|
|
|
pure $ "insert into " <> tab <> " values (" <> val <> ")"
|
2024-03-29 19:52:05 +00:00
|
|
|
de x =
|
|
|
|
Q.build do
|
2024-03-31 01:38:52 +00:00
|
|
|
x' <- Q.param $ fromArb x
|
|
|
|
let val = x' <> " :: " <> sql
|
|
|
|
pure $ "select " <> val
|
|
|
|
void $ exec createtab c
|
2024-03-29 19:52:05 +00:00
|
|
|
seed <- liftEffect randomSeed
|
2024-03-31 01:38:52 +00:00
|
|
|
let xs = sample seed 10 (arbitrary @x)
|
|
|
|
flip parTraverse_ xs
|
|
|
|
\x -> do
|
|
|
|
void $ exec (ser x) c
|
|
|
|
res <- query (de x) c
|
|
|
|
let
|
|
|
|
exp = fromArb x
|
|
|
|
act = unsafePartial fromJust $ Array.head res
|
|
|
|
when (not $ isEq exp act) $ fail $ "expected " <> show exp <> " to equal " <> show act
|
2024-03-27 02:59:28 +00:00
|
|
|
in
|
2024-03-31 01:38:52 +00:00
|
|
|
around withPoolClient
|
2024-03-29 19:52:05 +00:00
|
|
|
$ describe "Data.Postgres"
|
|
|
|
$ do
|
2024-03-31 01:38:52 +00:00
|
|
|
check @Int @GenSmallInt { purs: "Int", sql: "int2", fromArb: unwrap, isEq: eq }
|
|
|
|
check @Int { purs: "Int", sql: "int4", fromArb: identity, isEq: eq }
|
|
|
|
check @String @GenString { purs: "String", sql: "text", fromArb: unwrap, isEq: eq }
|
|
|
|
check @Boolean { purs: "Boolean", sql: "bool", fromArb: identity, isEq: eq }
|
|
|
|
check @Number @GenSmallFloat { purs: "Number", sql: "float4", fromArb: unwrap, isEq: \a b -> Number.abs (a - b) <= 0.0001 }
|
|
|
|
check @Number { purs: "Number", sql: "float8", fromArb: identity, isEq: eq }
|
|
|
|
check @BigInt @GenBigInt { purs: "BigInt", sql: "int8", fromArb: unwrap, isEq: eq }
|
|
|
|
check @DateTime @GenDateTime { purs: "DateTime", sql: "timestamptz", fromArb: unwrap, isEq: eq }
|
|
|
|
|
|
|
|
check @(Maybe String) @(Maybe GenString) { purs: "Maybe String", sql: "text", fromArb: map unwrap, isEq: eq }
|
|
|
|
check @(Array String) @(Array GenString) { purs: "Array String", sql: "text[]", fromArb: map unwrap, isEq: eq }
|
|
|
|
|
|
|
|
check @String @GenJSON { purs: "JSON", sql: "json", fromArb: writeJSON <<< unwrap, isEq: eq }
|