Skip to content

Commit da8150e

Browse files
QuickCheck tests for date/time types and numeric
1 parent 8b7741f commit da8150e

File tree

6 files changed

+195
-62
lines changed

6 files changed

+195
-62
lines changed

src/Database/PostgreSQL/Protocol/Codecs/Decoders.hs

+13-13
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ module Database.PostgreSQL.Protocol.Codecs.Decoders where
33
import Data.Word
44
import Data.Int
55
import Data.Maybe
6-
import Data.Fixed
76
import Data.Char
7+
import Data.Scientific
88
import Data.UUID (UUID, fromWords)
99
import Data.Time (Day, UTCTime, LocalTime, DiffTime)
1010
import qualified Data.ByteString as B
@@ -94,7 +94,7 @@ char _ = chr . fromIntegral <$> getWord8
9494

9595
{-# INLINE date #-}
9696
date :: FieldDecoder Day
97-
date _ = pgjToDay <$> getWord32BE
97+
date _ = pgjToDay <$> getInt32BE
9898

9999
{-# INLINE float4 #-}
100100
float4 :: FieldDecoder Float
@@ -130,15 +130,15 @@ bsJsonText = getByteString
130130
bsJsonBytes :: FieldDecoder B.ByteString
131131
bsJsonBytes len = getWord8 *> getByteString (len - 1)
132132

133-
numeric :: HasResolution a => FieldDecoder (Fixed a)
134-
numeric _ = do
135-
ndigits <- getWord16BE
136-
weight <- getInt16BE
137-
msign <- numericSign <$> getWord16BE
138-
sign <- maybe (fail "unknown numeric") pure msign
139-
dscale <- getWord16BE
140-
digits <- replicateM (fromIntegral ndigits) getWord16BE
141-
pure $ undefined
133+
{-# INLINE numeric #-}
134+
numeric :: FieldDecoder Scientific
135+
numeric _ = do
136+
ndigits <- getWord16BE
137+
weight <- getInt16BE
138+
sign <- getWord16BE >>= fromNumericSign
139+
_ <- getWord16BE
140+
numericToScientific sign weight <$>
141+
replicateM (fromIntegral ndigits) getWord16BE
142142

143143
-- | Decodes text without applying encoding.
144144
{-# INLINE bsText #-}
@@ -147,11 +147,11 @@ bsText = getByteString
147147

148148
{-# INLINE timestamp #-}
149149
timestamp :: FieldDecoder LocalTime
150-
timestamp _ = microsToLocalTime <$> getWord64BE
150+
timestamp _ = microsToLocalTime <$> getInt64BE
151151

152152
{-# INLINE timestamptz #-}
153153
timestamptz :: FieldDecoder UTCTime
154-
timestamptz _ = microsToUTC <$> getWord64BE
154+
timestamptz _ = microsToUTC <$> getInt64BE
155155

156156
{-# INLINE uuid #-}
157157
uuid :: FieldDecoder UUID

src/Database/PostgreSQL/Protocol/Codecs/Encoders.hs

+16-15
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import Data.Word
44
import Data.Monoid ((<>))
55
import Data.Int
66
import Data.Char
7-
import Data.Fixed
8-
import Data.UUID (UUID, toByteString)
7+
import Data.Scientific
8+
import Data.UUID (UUID, toWords)
99
import Data.Time (Day, UTCTime, LocalTime, DiffTime)
1010
import qualified Data.ByteString as B
1111
import qualified Data.Vector as V
@@ -35,7 +35,7 @@ char = putWord8 . fromIntegral . ord
3535

3636
{-# INLINE date #-}
3737
date :: Day -> Encode
38-
date = putWord32BE . dayToPgj
38+
date = putInt32BE . dayToPgj
3939

4040
{-# INLINE float4 #-}
4141
float4 :: Float -> Encode
@@ -72,15 +72,15 @@ bsJsonText = putByteString
7272
bsJsonBytes :: B.ByteString -> Encode
7373
bsJsonBytes bs = putWord8 1 <> putByteString bs
7474

75-
numeric :: HasResolution a => (Fixed a) -> Encode
76-
numeric _ = do undefined
77-
-- ndigits <- putWord16BE
78-
-- weight <- putInt16BE
79-
-- msign <- numericSign <$> putWord16BE
80-
-- sign <- maybe (fail "unknown numeric") pure msign
81-
-- dscale <- putWord16BE
82-
-- digits <- replicateM (fromIntegral ndigits) putWord16BE
83-
-- pure $ undefined
75+
{-# INLINE numeric #-}
76+
numeric :: Scientific -> Encode
77+
numeric n =
78+
let (weight, scale, digits) = scientificToNumeric n
79+
in putWord16BE (fromIntegral $ length digits)
80+
<> putInt16BE weight
81+
<> putWord16BE (toNumericSign n)
82+
<> putWord16BE scale
83+
<> foldMap putWord16BE digits
8484

8585
-- | Encodes text.
8686
{-# INLINE bsText #-}
@@ -89,12 +89,13 @@ bsText = putByteString
8989

9090
{-# INLINE timestamp #-}
9191
timestamp :: LocalTime -> Encode
92-
timestamp = putWord64BE . localTimeToMicros
92+
timestamp = putInt64BE . localTimeToMicros
9393

9494
{-# INLINE timestamptz #-}
9595
timestamptz :: UTCTime -> Encode
96-
timestamptz = putWord64BE . utcToMicros
96+
timestamptz = putInt64BE . utcToMicros
9797

9898
{-# INLINE uuid #-}
9999
uuid :: UUID -> Encode
100-
uuid = undefined
100+
uuid v = let (a, b, c, d) = toWords v
101+
in putWord32BE a <> putWord32BE b <> putWord32BE c <> putWord32BE d
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,51 @@
1+
{-# language LambdaCase #-}
12
module Database.PostgreSQL.Protocol.Codecs.Numeric where
23

34
-- TODO test it
5+
import Data.Tuple
46
import Data.Word
57
import Data.Int
68
import Data.Foldable
7-
import Data.Fixed
9+
import Data.Scientific
10+
import Data.List (unfoldr)
811

9-
numericDigit :: [Word16] -> Integer
10-
numericDigit = foldl' (\acc n -> acc * nBase + fromIntegral n) 0
12+
integerToDigits :: Integer -> [Word16]
13+
integerToDigits = (reverse.) . unfoldr $ \case
14+
0 -> Nothing
15+
n -> let (rest, rem) = n `divMod` nBase in Just (fromIntegral rem, rest)
1116

12-
numericSign :: Num a => Word16 -> Maybe a
13-
numericSign 0x0000 = Just 1
14-
numericSign 0x4000 = Just $ -1
15-
numericSign _ = Nothing -- NaN code is 0xC000, it is not supported.
17+
toNumericSign :: Scientific -> Word16
18+
toNumericSign s | s >= 0 = 0x0000
19+
| otherwise = 0x4000
1620

17-
fixedFromNumeric :: HasResolution a => Int16 -> [Word16] -> Fixed a
18-
fixedFromNumeric weight digits = undefined
21+
scientificToNumeric :: Scientific -> (Int16, Word16, [Word16])
22+
scientificToNumeric number =
23+
let a = base10Exponent number `mod` nBaseDigits
24+
adjExp = base10Exponent number - a
25+
adjCoef = coefficient number * (10 ^ a)
26+
digits = integerToDigits $ abs adjCoef
27+
weight = fromIntegral $ length digits + adjExp `div` nBaseDigits - 1
28+
scale = fromIntegral . negate $ min (base10Exponent number) 0
29+
in (weight, scale, digits)
30+
31+
digitsToInteger :: [Word16] -> Integer
32+
digitsToInteger = foldl' (\acc n -> acc * nBase + fromIntegral n) 0
33+
34+
fromNumericSign :: (Monad m, Num a) => Word16 -> m a
35+
fromNumericSign 0x0000 = pure 1
36+
fromNumericSign 0x4000 = pure $ -1
37+
-- NaN code is 0xC000, it is not supported.
38+
fromNumericSign _ = fail "Unknown numeric sign"
39+
40+
numericToScientific :: Integer -> Int16 -> [Word16] -> Scientific
41+
numericToScientific sign weight digits =
42+
let coef = digitsToInteger digits * sign
43+
exp' = (fromIntegral weight + 1 - length digits) * nBaseDigits
44+
in scientific coef exp'
1945

2046
nBase :: Num a => a
2147
nBase = 10000
48+
49+
nBaseDigits :: Num a => a
50+
nBaseDigits = 4
51+

src/Database/PostgreSQL/Protocol/Codecs/Time.hs

+9-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ module Database.PostgreSQL.Protocol.Codecs.Time
99
, diffTimeToInterval
1010
) where
1111

12-
import Data.Int (Int64, Int32)
13-
import Data.Word (Word32, Word64)
12+
import Data.Int (Int64, Int32, Int64)
1413
import Data.Time (Day(..), UTCTime(..), LocalTime(..), DiffTime, TimeOfDay,
1514
picosecondsToDiffTime, timeToTimeOfDay,
1615
diffTimeToPicoseconds, timeOfDayToTime)
@@ -21,11 +20,11 @@ dayToPgj = fromIntegral
2120
.(+ (modifiedJulianEpoch - postgresEpoch)) . toModifiedJulianDay
2221

2322
{-# INLINE utcToMicros #-}
24-
utcToMicros :: UTCTime -> Word64
23+
utcToMicros :: UTCTime -> Int64
2524
utcToMicros (UTCTime day diffTime) = dayToMcs day + diffTimeToMcs diffTime
2625

2726
{-# INLINE localTimeToMicros #-}
28-
localTimeToMicros :: LocalTime -> Word64
27+
localTimeToMicros :: LocalTime -> Int64
2928
localTimeToMicros (LocalTime day time) = dayToMcs day + timeOfDayToMcs time
3029

3130
{-# INLINE pgjToDay #-}
@@ -34,13 +33,13 @@ pgjToDay = ModifiedJulianDay . fromIntegral
3433
. subtract (modifiedJulianEpoch - postgresEpoch)
3534

3635
{-# INLINE microsToUTC #-}
37-
microsToUTC :: Word64 -> UTCTime
36+
microsToUTC :: Int64 -> UTCTime
3837
microsToUTC mcs =
3938
let (d, r) = mcs `divMod` microsInDay
4039
in UTCTime (pgjToDay d) (mcsToDiffTime r)
4140

4241
{-# INLINE microsToLocalTime #-}
43-
microsToLocalTime :: Word64 -> LocalTime
42+
microsToLocalTime :: Int64 -> LocalTime
4443
microsToLocalTime mcs =
4544
let (d, r) = mcs `divMod` microsInDay
4645
in LocalTime (pgjToDay d) (mcsToTimeOfDay r)
@@ -87,14 +86,18 @@ pcsToMcs = (`div` 10 ^ 6)
8786
mcsToPcs :: Integral a => a -> a
8887
mcsToPcs = (* 10 ^ 6)
8988

89+
{-# INLINE modifiedJulianEpoch #-}
9090
modifiedJulianEpoch :: Num a => a
9191
modifiedJulianEpoch = 2400001
9292

93+
{-# INLINE postgresEpoch #-}
9394
postgresEpoch :: Num a => a
9495
postgresEpoch = 2451545
9596

97+
{-# INLINE microsInDay #-}
9698
microsInDay :: Num a => a
9799
microsInDay = 24 * 60 * 60 * 10 ^ 6
98100

101+
{-# INLINE daysInMonth #-}
99102
daysInMonth :: Num a => a
100103
daysInMonth = 30

0 commit comments

Comments
 (0)