module PostgreSQL.Binary.Encoder
(
run,
Encoder,
int2_int16,
int2_word16,
int4_int32,
int4_word32,
int8_int64,
int8_word64,
float4,
float8,
composite,
bool,
numeric,
uuid,
json,
char,
text_strict,
text_lazy,
bytea_strict,
bytea_lazy,
date,
time_int,
time_float,
timetz_int,
timetz_float,
timestamp_int,
timestamp_float,
timestamptz_int,
timestamptz_float,
interval_int,
interval_float,
hstore,
hstoreRep,
array,
ArrayEncoder,
arrayValue,
arrayNullableValue,
arrayDimension,
arrayRep,
enum,
)
where
import PostgreSQL.Binary.Prelude hiding (take, bool, maybe)
import Data.ByteString.Builder (Builder)
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Lazy as LazyByteString
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.Text.Lazy as LazyText
import qualified Data.Text.Lazy.Encoding as LazyText
import qualified Data.Vector as Vector
import qualified Data.Scientific as Scientific
import qualified Data.Aeson as Aeson
import qualified Data.UUID as UUID
import qualified PostgreSQL.Binary.Data as Data
import qualified PostgreSQL.Binary.Integral as Integral
import qualified PostgreSQL.Binary.Numeric as Numeric
import qualified PostgreSQL.Binary.Time as Time
import qualified PostgreSQL.Binary.Interval as Interval
import qualified PostgreSQL.Binary.BuilderPrim as BuilderPrim
import qualified Control.Foldl as Foldl
type Encoder a =
a -> Builder
run :: Encoder a -> a -> ByteString
run encoder =
LazyByteString.toStrict . Builder.toLazyByteString . encoder
tuple2 :: Encoder a -> Encoder b -> Encoder (a, b)
tuple2 e1 e2 =
\(v1, v2) -> e1 v1 <> e2 v2
tuple3 :: Encoder a -> Encoder b -> Encoder c -> Encoder (a, b, c)
tuple3 e1 e2 e3 =
\(v1, v2, v3) -> e1 v1 <> e2 v2 <> e3 v3
tuple4 :: Encoder a -> Encoder b -> Encoder c -> Encoder d -> Encoder (a, b, c, d)
tuple4 e1 e2 e3 e4 =
\(v1, v2, v3, v4) -> e1 v1 <> e2 v2 <> e3 v3 <> e4 v4
premap :: (a -> b) -> Encoder b -> Encoder a
premap f e =
e . f
int2_int16 :: Encoder Int16
int2_int16 =
Builder.int16BE
int2_word16 :: Encoder Word16
int2_word16 =
Builder.word16BE
int4_int32 :: Encoder Int32
int4_int32 =
Builder.int32BE
int4_word32 :: Encoder Word32
int4_word32 =
Builder.word32BE
int4_int :: Encoder Int
int4_int =
int4_int32 . fromIntegral
int8_int64 :: Encoder Int64
int8_int64 =
Builder.int64BE
int8_word64 :: Encoder Word64
int8_word64 =
Builder.word64BE
float4 :: Encoder Float
float4 =
int4_int32 . unsafeCoerce
float8 :: Encoder Double
float8 =
int8_int64 . unsafeCoerce
null4 :: ByteStringBuilder
null4 =
Builder.string7 "\255\255\255\255"
composite :: Encoder Data.Composite
composite vector =
int4_int (Vector.length vector) <>
foldMap component vector
where
component (oid, theContent) =
int4_word32 oid <> content theContent
content :: Encoder Data.Content
content =
\case
Nothing ->
null4
Just content ->
int4_int (ByteString.length content) <>
Builder.byteString content
maybe :: Encoder a -> Encoder (Maybe a)
maybe encoder =
\case
Nothing ->
null4
Just value ->
run encoder value & \bytes -> int4_int (ByteString.length bytes) <> Builder.byteString bytes
bool :: Encoder Bool
bool =
\case
True -> Builder.word8 1
False -> Builder.word8 0
numeric :: Encoder Scientific
numeric x =
int2_int16 (fromIntegral componentsAmount) <>
int2_int16 (fromIntegral pointIndex) <>
int2_word16 signCode <>
int2_int16 (fromIntegral trimmedExponent) <>
foldMap int2_int16 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
uuid :: Encoder UUID
uuid =
premap UUID.toWords (tuple4 int4_word32 int4_word32 int4_word32 int4_word32)
json :: Encoder Aeson.Value
#if MIN_VERSION_aeson(0,10,0)
json =
Aeson.fromEncoding . Aeson.toEncoding
#else
json =
Builder.lazyByteString . Aeson.encode
#endif
char :: Encoder Char
char =
Builder.charUtf8
text_strict :: Encoder Text
text_strict =
Text.encodeUtf8BuilderEscaped BuilderPrim.nullByteIgnoringBoundedPrim
text_lazy :: Encoder LazyText.Text
text_lazy =
LazyText.encodeUtf8BuilderEscaped BuilderPrim.nullByteIgnoringBoundedPrim
bytea_strict :: Encoder ByteString
bytea_strict =
Builder.byteString
bytea_lazy :: Encoder LazyByteString.ByteString
bytea_lazy =
Builder.lazyByteString
date :: Encoder Day
date =
int4_int32 . fromIntegral . Time.dayToPostgresJulian
time_int :: Encoder TimeOfDay
time_int (TimeOfDay h m s) =
let
p = unsafeCoerce s :: Integer
u = p `div` (10^6)
in int8_int64 (fromIntegral u + 10^6 * 60 * (fromIntegral m + 60 * fromIntegral h))
time_float :: Encoder TimeOfDay
time_float (TimeOfDay h m s) =
let
p = unsafeCoerce s :: Integer
u = p `div` (10^6)
in float8 (fromIntegral u / 10^6 + 60 * (fromIntegral m + 60 * (fromIntegral h)))
timetz_int :: Encoder (TimeOfDay, TimeZone)
timetz_int (timeX, tzX) =
time_int timeX <> tz tzX
timetz_float :: Encoder (TimeOfDay, TimeZone)
timetz_float (timeX, tzX) =
time_float timeX <> tz tzX
tz :: Encoder TimeZone
tz =
int4_int . (*60) . negate . timeZoneMinutes
timestamp_int :: Encoder LocalTime
timestamp_int =
int8_int64 . Time.localTimeToMicros
timestamp_float :: Encoder LocalTime
timestamp_float =
float8 . Time.localTimeToSecs
timestamptz_int :: Encoder UTCTime
timestamptz_int =
int8_int64 . Time.utcToMicros
timestamptz_float :: Encoder UTCTime
timestamptz_float =
float8 . Time.utcToSecs
interval_int :: Encoder DiffTime
interval_int x =
Builder.int64BE u <>
Builder.int32BE d <>
Builder.int32BE m
where
Interval.Interval u d m =
fromMaybe (error ("Too large DiffTime value for an interval " <> show x)) $
Interval.fromDiffTime x
interval_float :: Encoder DiffTime
interval_float x =
Builder.doubleBE s <>
Builder.int32BE d <>
Builder.int32BE m
where
Interval.Interval u d m =
fromMaybe (error ("Too large DiffTime value for an interval " <> show x)) $
Interval.fromDiffTime x
s =
fromIntegral u / (10^6)
newtype ArrayEncoder a =
ArrayEncoder (a -> (Builder, [Int32], Bool))
array :: Word32 -> ArrayEncoder a -> Encoder a
array oid (ArrayEncoder encoder) =
\value ->
let
(valuesBuilder, dimensions, nulls) =
encoder value
(dimensionsAmount, dimensionsBuilder) =
let
step (amount, builder) dimension =
(succ amount, builder <> Builder.int32BE dimension <> Builder.word32BE 1)
init =
(0, mempty)
in
foldl' step init dimensions
nullsBuilder =
Builder.word32BE (if nulls then 1 else 0)
in
Builder.word32BE dimensionsAmount <> nullsBuilder <> Builder.word32BE oid <> dimensionsBuilder <> valuesBuilder
arrayValue :: Encoder a -> ArrayEncoder a
arrayValue encoder =
ArrayEncoder $ \value ->
let
bytes =
run encoder value
builder =
Builder.word32BE (fromIntegral (ByteString.length bytes)) <>
Builder.byteString bytes
in
(builder, [], False)
arrayNullableValue :: Encoder a -> ArrayEncoder (Maybe a)
arrayNullableValue encoder =
ArrayEncoder $ \case
Nothing ->
(int4_int32 (1), [], True)
Just value ->
let
bytes =
run encoder value
builder =
Builder.word32BE (fromIntegral (ByteString.length bytes)) <>
Builder.byteString bytes
in
(builder, [], False)
arrayDimension :: (forall a. (a -> b -> a) -> a -> c -> a) -> ArrayEncoder b -> ArrayEncoder c
arrayDimension foldl (ArrayEncoder encoder) =
ArrayEncoder $ \value ->
let
step (builder, _, length, nulls) value =
let
(valueBuilder, valueDimensions, valueNulls) = encoder value
in
(builder <> valueBuilder, valueDimensions, succ length, nulls || valueNulls)
init =
(mempty, [], 0, False)
(foldedBuilder, foldedDimensions, foldedLength, foldedNulls) =
foldl step init value
resultDimensions =
foldedLength : foldedDimensions
in
(foldedBuilder, resultDimensions, foldedNulls)
arrayRep :: Encoder Data.Array
arrayRep (dimensionsV, valuesV, nullsV, oidV) =
dimensionsLength <> nulls <> oid <> dimensions <> values
where
dimensionsLength =
int4_word32 $ fromIntegral $ Vector.length dimensionsV
nulls =
int4_word32 $ if nullsV then 1 else 0
oid =
int4_word32 oidV
dimensions =
foldMap dimension dimensionsV
values =
foldMap value valuesV
dimension (w, l) =
int4_word32 w <> int4_word32 l
value =
\case
Nothing -> int4_int32 (1)
Just b -> int4_int32 (fromIntegral (ByteString.length b)) <> Builder.byteString b
hstore :: (forall a. (a -> (Text, Maybe Text) -> a) -> a -> b -> a) -> Encoder b
hstore foldl =
fold & \(Foldl.Fold step init fin) -> fin . foldl step init
where
fold =
(<>) <$> componentsAmount <*> components
where
componentsAmount =
fmap int4_int Foldl.length
components =
Foldl.foldMap componentBuilder id
where
componentBuilder (key, value) =
text_strict key <> maybe text_strict value
hstoreRep :: Encoder Data.HStore
hstoreRep vector =
int4_int32 (fromIntegral (Vector.length vector)) <>
foldMap component vector
where
component (key, value) =
Builder.byteString key <> content value
enum :: (a -> Text) -> Encoder a
enum asText =
text_strict . asText