postgresql-binary-0.2.0: library/PostgreSQLBinary/Encoder/Builder.hs
module PostgreSQLBinary.Encoder.Builder where
import PostgreSQLBinary.Prelude hiding (bool)
import Data.ByteString.Builder
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import qualified Data.Scientific as Scientific
import qualified Data.UUID as UUID
import qualified PostgreSQLBinary.Array as Array
import qualified PostgreSQLBinary.Time as Time
import qualified PostgreSQLBinary.Numeric as Numeric
{-# INLINE run #-}
run :: Builder -> B.ByteString
run =
BL.toStrict . toLazyByteString
{-# INLINE bool #-}
bool :: Bool -> Builder
bool =
\case True -> word8 1; False -> word8 0
{-# INLINE array #-}
array :: Array.Data -> Builder
array (dimensionsV, valuesV, nullsV, oidV) =
dimensionsLength <> nulls <> oid <> dimensions <> values
where
dimensionsLength =
word32BE $ fromIntegral $ length dimensionsV
nulls =
word32BE $ if nullsV then 1 else 0
oid =
word32BE oidV
dimensions =
foldMap dimension dimensionsV
values =
foldMap value valuesV
dimension (w, l) =
word32BE w <> word32BE l
value =
\case
Nothing -> word32BE (-1)
Just b -> word32BE (fromIntegral (B.length b)) <> byteString b
{-# INLINE date #-}
date :: Day -> Builder
date =
int32BE . fromIntegral . Time.dayToPostgresJulian
{-# INLINE timestamp #-}
timestamp :: UTCTime -> Builder
timestamp (UTCTime dayX timeX) =
let days = Time.dayToPostgresJulian dayX * 10^6 * 60 * 60 * 24
time = (`div` (10^6)) . unsafeCoerce $ timeX
in int64BE $ fromIntegral $ days + time
{-# INLINE timestamptz #-}
timestamptz :: LocalTime -> Builder
timestamptz (LocalTime dayX timeX) =
let days = Time.dayToPostgresJulian dayX * 10^6 * 60 * 60 * 24
time = (`div` (10^6)) . unsafeCoerce timeOfDayToTime $ timeX
in int64BE $ fromIntegral $ days + time
{-# INLINE interval #-}
interval :: DiffTime -> Builder
interval x =
flip evalState (unsafeCoerce x :: Integer) $ do
u <- state (`divMod` (10 ^ 6))
d <- state (`divMod` (10 ^ 6 * 60 * 60 * 24))
m <- get
return $
int64BE (fromIntegral u) <> int32BE (fromIntegral d) <> int32BE (fromIntegral m)
{-# INLINE numeric #-}
numeric :: Scientific -> Builder
numeric x =
word16BE (fromIntegral componentsAmount) <>
int16BE (fromIntegral pointIndex) <>
word16BE signCode <>
word16BE (fromIntegral trimmedExponent) <>
foldMap word16BE components
where
componentsAmount =
length components
coefficient =
Scientific.coefficient x
exponent =
Scientific.base10Exponent x
components =
Numeric.extractComponents tunedCoefficient
pointIndex =
componentsAmount + (tunedExponent `div` 4) - 1
(tunedCoefficient, tunedExponent) =
case mod exponent 4 of
0 -> (coefficient, exponent)
x -> (coefficient * 10 ^ x, exponent - x)
trimmedExponent =
if tunedExponent >= 0
then 0
else negate tunedExponent
signCode =
if coefficient < 0
then Numeric.negSignCode
else Numeric.posSignCode
{-# INLINE uuid #-}
uuid :: UUID -> Builder
uuid =
UUID.toWords >>> \(a, b, c, d) ->
word32BE a <>
word32BE b <>
word32BE c <>
word32BE d