postgresql-binary 0.10 → 0.12.1
raw patch · 15 files changed
+1534/−1410 lines, 15 filesdep +bytestring-strict-builderdep +containersdep +unordered-containersdep −foldl
Dependencies added: bytestring-strict-builder, containers, unordered-containers
Dependencies removed: foldl
Files
- decoding/Main.hs +25/−28
- encoding/Main.hs +22/−25
- library/PostgreSQL/Binary/Data.hs +24/−57
- library/PostgreSQL/Binary/Decoder.hs +0/−580
- library/PostgreSQL/Binary/Decoding.hs +535/−0
- library/PostgreSQL/Binary/Encoder.hs +0/−576
- library/PostgreSQL/Binary/Encoding.hs +347/−0
- library/PostgreSQL/Binary/Encoding/Builders.hs +456/−0
- library/PostgreSQL/Binary/Numeric.hs +1/−1
- library/PostgreSQL/Binary/Prelude.hs +8/−0
- postgresql-binary.cabal +8/−9
- tasty/Main.hs +92/−87
- tasty/Main/Gens.hs +1/−30
- tasty/Main/IO.hs +9/−10
- tasty/Main/Properties.hs +6/−7
decoding/Main.hs view
@@ -3,61 +3,58 @@ import Prelude import Criterion import Criterion.Main-import qualified PostgreSQL.Binary.Encoder as E-import qualified PostgreSQL.Binary.Decoder as D+import qualified PostgreSQL.Binary.Encoding as E+import qualified PostgreSQL.Binary.Decoding as D main = defaultMain [- b "bool" D.bool (E.run E.bool True)+ b "bool" D.bool ((E.encodingBytes . E.bool) True) ,- b "int2" (D.int :: D.Decoder Int16) (E.run E.int2_int16 1000)+ b "int2" (D.int :: D.Value Int16) ((E.encodingBytes . E.int2_int16) 1000) ,- b "int4" (D.int :: D.Decoder Int32) (E.run E.int4_int32 1000)+ b "int4" (D.int :: D.Value Int32) ((E.encodingBytes . E.int4_int32) 1000) ,- b "int8" (D.int :: D.Decoder Int64) (E.run E.int8_int64 1000)+ b "int8" (D.int :: D.Value Int64) ((E.encodingBytes . E.int8_int64) 1000) ,- b "float4" D.float4 (E.run E.float4 12.65468468)+ b "float4" D.float4 ((E.encodingBytes . E.float4) 12.65468468) ,- b "float8" D.float8 (E.run E.float8 12.65468468)+ b "float8" D.float8 ((E.encodingBytes . E.float8) 12.65468468) ,- b "numeric" D.numeric (E.run E.numeric (read "20.213290183"))+ b "numeric" D.numeric ((E.encodingBytes . E.numeric) (read "20.213290183")) ,- b "char" D.char (E.run E.char 'Я')+ b "char" D.char ((E.encodingBytes . E.char_utf8) 'Я') ,- b "text" D.text_strict (E.run E.text_strict "alsdjflskjдывлоаы оады")+ b "text" D.text_strict ((E.encodingBytes . E.text_strict) "alsdjflskjдывлоаы оады") ,- b "bytea" D.bytea_strict (E.run E.bytea_strict "alskdfj;dasjfl;dasjflksdj")+ b "bytea" D.bytea_strict ((E.encodingBytes . E.bytea_strict) "alskdfj;dasjfl;dasjflksdj") ,- b "date" D.date (E.run E.date (read "2000-01-19"))+ b "date" D.date ((E.encodingBytes . E.date) (read "2000-01-19")) ,- b "time" D.time_int (E.run E.time_int (read "10:41:06"))+ b "time" D.time_int ((E.encodingBytes . E.time_int) (read "10:41:06")) ,- b "timetz" D.timetz_int (E.run E.timetz_int (read "(10:41:06, +0300)"))+ b "timetz" D.timetz_int ((E.encodingBytes . E.timetz_int) (read "(10:41:06, +0300)")) ,- b "timestamp" D.timestamp_int (E.run E.timestamp_int (read "2000-01-19 10:41:06"))+ b "timestamp" D.timestamp_int ((E.encodingBytes . E.timestamp_int) (read "2000-01-19 10:41:06")) ,- b "timestamptz" D.timestamptz_int (E.run E.timestamptz_int (read "2000-01-19 10:41:06"))+ b "timestamptz" D.timestamptz_int ((E.encodingBytes . E.timestamptz_int) (read "2000-01-19 10:41:06")) ,- b "interval" D.interval_int (E.run E.interval_int (secondsToDiffTime 23472391128374))+ b "interval" D.interval_int ((E.encodingBytes . E.interval_int) (secondsToDiffTime 23472391128374)) ,- b "uuid" D.uuid (E.run E.uuid (read "550e8400-e29b-41d4-a716-446655440000"))+ b "uuid" D.uuid ((E.encodingBytes . E.uuid) (read "550e8400-e29b-41d4-a716-446655440000")) , let encoder =- E.array 23 $- E.arrayDimension foldl' $- E.arrayValue $- E.int4_int32+ E.array 23 . E.dimensionArray foldl' (E.encodingArray . E.int4_int32) decoder = D.array $- D.arrayDimension replicateM $- D.arrayNonNullValue $- (D.int :: D.Decoder Int32)+ D.dimensionArray replicateM $+ D.valueArray $+ (D.int :: D.Value Int32) in- b "array" decoder (E.run encoder [1,2,3,4])+ b "array" decoder (E.encodingBytes (encoder [1,2,3,4])) ] where b name decoder value = - bench name $ nf (D.run decoder) value+ bench name $ nf (D.valueParser decoder) value
encoding/Main.hs view
@@ -3,55 +3,52 @@ import Prelude import Criterion import Criterion.Main-import qualified PostgreSQL.Binary.Encoder as E+import qualified PostgreSQL.Binary.Encoding as E main = defaultMain [- b "bool" E.bool True+ value "bool" E.bool True ,- b "int2" E.int2_int16 1000+ value "int2" E.int2_int16 1000 ,- b "int4" E.int4_int32 1000+ value "int4" E.int4_int32 1000 ,- b "int8" E.int8_int64 1000+ value "int8" E.int8_int64 1000 ,- b "float4" E.float4 12.65468468+ value "float4" E.float4 12.65468468 ,- b "float8" E.float8 12.65468468+ value "float8" E.float8 12.65468468 ,- b "numeric" E.numeric (read "20.213290183")+ value "numeric" E.numeric (read "20.213290183") ,- b "char" E.char 'Я'+ value "char_utf8" E.char_utf8 'Я' ,- b "text" E.text_strict "alsdjflskjдывлоаы оады"+ value "text" E.text_strict "alsdjflskjдывлоаы оады" ,- b "bytea" E.bytea_strict "alskdfj;dasjfl;dasjflksdj"+ value "bytea" E.bytea_strict "alskdfj;dasjfl;dasjflksdj" ,- b "date" E.date (read "2000-01-19")+ value "date" E.date (read "2000-01-19") ,- b "time" E.time_int (read "10:41:06")+ value "time" E.time_int (read "10:41:06") ,- b "timetz" E.timetz_int (read "(10:41:06, +0300)")+ value "timetz" E.timetz_int (read "(10:41:06, +0300)") ,- b "timestamp" E.timestamp_int (read "2000-01-19 10:41:06")+ value "timestamp" E.timestamp_int (read "2000-01-19 10:41:06") ,- b "timestamptz" E.timestamptz_int (read "2000-01-19 10:41:06")+ value "timestamptz" E.timestamptz_int (read "2000-01-19 10:41:06") ,- b "interval" E.interval_int (secondsToDiffTime 23472391128374)+ value "interval" E.interval_int (secondsToDiffTime 23472391128374) ,- b "uuid" E.uuid (read "550e8400-e29b-41d4-a716-446655440000")+ value "uuid" E.uuid (read "550e8400-e29b-41d4-a716-446655440000") , let encoder =- E.array 23 $- E.arrayDimension foldl' $- E.arrayValue $- E.int4_int32+ E.array 23 . E.dimensionArray foldl' (E.encodingArray . E.int4_int32) in- b "array" encoder [1,2,3,4]+ value "array" encoder [1,2,3,4] ] where- b name encoder value = - bench name $ nf (E.run encoder) value+ value name encoder value = + bench name $ nf (E.encodingBytes . encoder) value
library/PostgreSQL/Binary/Data.hs view
@@ -1,58 +1,25 @@--- |--- Models of supported data structures according to the serialisation format.-module PostgreSQL.Binary.Data where--import PostgreSQL.Binary.Prelude-import qualified Network.IP.Addr as IPAddr---- | --- A representation of a data serializable to the PostgreSQL array binary format.--- --- Consists of a vector of dimensions, a vector of encoded elements,--- a flag specifying, whether it contains any nulls, and an oid.-type Array =- (Vector ArrayDimension, Vector Content, Bool, OID)---- | --- A width and a lower bound.--- --- Currently the lower bound is only allowed to have a value of @1@.-type ArrayDimension =- (Word32, Word32)---- |--- An encoded value. 'Nothing' if it represents a @NULL@.-type Content =- Maybe ByteString---- |--- A Postgres OID of a type.-type OID =- Word32---- |--- A representation of a composite Postgres data (Record or Row).-type Composite =- Vector (OID, Content)---- |--- HStore.-type HStore =- Vector (ByteString, Content)---- |--- The four components of UUID.-type UUID =- (Word32, Word32, Word32, Word32)+{-|+Reexports of all the data-types that this API supports.+Useful for the reduction of dependencies in the \"postgresql-binary\" dependent libraries.+-}+module PostgreSQL.Binary.Data+(+ module Data.HashMap.Strict,+ module Data.Map.Strict,+ module Data.Scientific,+ module Data.Time,+ module Data.UUID,+ module Data.Vector,+ module Data.Aeson,+ module Network.IP.Addr,+)+where --- |--- Representation of the PostgreSQL Numeric encoding.--- --- Consists of the following components:--- --- * Point index--- * Sign code--- * Components--- -type Numeric =- (Int16, Word16, Vector Int16)+import Data.HashMap.Strict (HashMap)+import Data.Map.Strict (Map)+import Data.Scientific (Scientific)+import Data.Time (Day, TimeOfDay, TimeZone, LocalTime, UTCTime, DiffTime)+import Data.UUID (UUID)+import Data.Vector (Vector)+import Data.Aeson (Value)+import Network.IP.Addr (IP, NetAddr)
− library/PostgreSQL/Binary/Decoder.hs
@@ -1,580 +0,0 @@-module PostgreSQL.Binary.Decoder-(- Decoder,- run,- -- * Primitive- int,- float4,- float8,- bool,- bytea_strict,- bytea_lazy,- -- * Textual- text_strict,- text_lazy,- char,- -- * Misc- fn,- numeric,- uuid,- inet,- json_ast,- json_bytes,- jsonb_ast,- jsonb_bytes,- -- * Time- date,- time_int,- time_float,- timetz_int,- timetz_float,- timestamp_int,- timestamp_float,- timestamptz_int,- timestamptz_float,- interval_int,- interval_float,- -- * Exotic- -- ** Array- ArrayDecoder,- array,- arrayDimension,- arrayValue,- arrayNonNullValue,- -- ** Composite- CompositeDecoder,- composite,- compositeValue,- compositeNonNullValue,- -- ** HStore- hstore,- -- **- enum,-)-where--import PostgreSQL.Binary.Prelude hiding (take, bool, drop, state, fail, failure)-import BinaryParser-import qualified PostgreSQL.Binary.Data as Data-import qualified PostgreSQL.Binary.Integral as Integral-import qualified PostgreSQL.Binary.Interval as Interval-import qualified PostgreSQL.Binary.Numeric as Numeric-import qualified PostgreSQL.Binary.Time as Time-import qualified PostgreSQL.Binary.Inet as Inet-import qualified Data.Vector as Vector-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.Encoding.Error as Text-import qualified Data.Text.Lazy.Encoding as LazyText-import qualified Data.UUID as UUID-import qualified Data.Aeson as Aeson-import qualified Network.IP.Addr as IPAddr---type Decoder =- BinaryParser----- * Helpers------------------------------ |--- Any int number of a limited byte-size.-{-# INLINE intOfSize #-}-intOfSize :: (Integral a, Bits a) => Int -> Decoder a-intOfSize x =- fmap Integral.pack (bytesOfSize x)--{-# INLINABLE onContent #-}-onContent :: Decoder a -> Decoder ( Maybe a )-onContent decoder =- size >>=- \case- (-1) -> pure Nothing- n -> fmap Just (sized (fromIntegral n) decoder)- where- size =- intOfSize 4 :: Decoder Int32--{-# INLINABLE content #-}-content :: Decoder (Maybe ByteString)-content =- intOfSize 4 >>= \case- (-1) -> pure Nothing- n -> fmap Just (bytesOfSize n)--{-# INLINE nonNull #-}-nonNull :: Maybe a -> Decoder a-nonNull =- maybe (failure "Unexpected NULL") return----- * Primitive------------------------------ |--- Lifts a custom decoder implementation.-{-# INLINE fn #-}-fn :: (ByteString -> Either Text a) -> Decoder a-fn fn =- BinaryParser.remainders >>= either BinaryParser.failure return . fn--{-# INLINE int #-}-int :: (Integral a, Bits a) => Decoder a-int =- fmap Integral.pack remainders--float4 :: Decoder Float-float4 =- unsafeCoerce (int :: Decoder Int32)--float8 :: Decoder Double-float8 =- unsafeCoerce (int :: Decoder Int64)--{-# INLINE bool #-}-bool :: Decoder Bool-bool =- fmap (== 1) byte--{-# NOINLINE numeric #-}-numeric :: Decoder Scientific-numeric =- do- componentsAmount <- intOfSize 2- pointIndex <- intOfSize 2- signCode <- intOfSize 2- unitOfSize 2- components <- Vector.replicateM componentsAmount (intOfSize 2)- either failure return (Numeric.scientific pointIndex signCode components)--{-# INLINABLE uuid #-}-uuid :: Decoder UUID-uuid =- UUID.fromWords <$> intOfSize 4 <*> intOfSize 4 <*> intOfSize 4 <*> intOfSize 4--{-# INLINE ip4 #-}-ip4 :: Decoder IPAddr.IP4-ip4 =- IPAddr.ip4FromOctets <$> intOfSize 1 <*> intOfSize 1 <*> intOfSize 1 <*> intOfSize 1--{-# INLINE ip6 #-}-ip6 :: Decoder IPAddr.IP6-ip6 =- IPAddr.ip6FromWords <$> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2--{-# INLINABLE inet #-}-inet :: Decoder (IPAddr.NetAddr IPAddr.IP)-inet = do- af <- intOfSize 1- netmask <- intOfSize 1- isCidr <- intOfSize 1- ipSize <- intOfSize 1- if | af == Inet.inetAddressFamily ->- do ip <- ip4- return $ inetFromBytes af netmask isCidr ipSize (IPAddr.IPv4 ip)- | af == Inet.inet6AddressFamily ->- do ip <- ip6- return $ inetFromBytes af netmask isCidr ipSize (IPAddr.IPv6 ip)- | otherwise -> BinaryParser.failure ("Unknown address family: " <> fromString (show af))- where- inetFromBytes :: Word8 -> Word8 -> Word8 -> Int8 -> IPAddr.IP -> IPAddr.NetAddr IPAddr.IP- inetFromBytes _ netmask _ _ ip = IPAddr.netAddr ip netmask--{-# INLINABLE json_ast #-}-json_ast :: Decoder Aeson.Value-json_ast =- bytea_strict >>= either (BinaryParser.failure . fromString) pure . Aeson.eitherDecodeStrict'---- |--- Given a function, which parses a plain UTF-8 JSON string encoded as a byte-array,--- produces a decoder.-{-# INLINABLE json_bytes #-}-json_bytes :: (ByteString -> Either Text a) -> Decoder a-json_bytes cont =- getAllBytes >>= parseJSON- where- getAllBytes =- BinaryParser.remainders- parseJSON =- either BinaryParser.failure return . cont--{-# INLINABLE jsonb_ast #-}-jsonb_ast :: Decoder Aeson.Value-jsonb_ast =- jsonb_bytes $ mapLeft fromString . Aeson.eitherDecodeStrict'---- |--- Given a function, which parses a plain UTF-8 JSON string encoded as a byte-array,--- produces a decoder.--- --- For those wondering, yes,--- JSONB is encoded as plain JSON string in the binary format of Postgres.--- Sad, but true.-{-# INLINABLE jsonb_bytes #-}-jsonb_bytes :: (ByteString -> Either Text a) -> Decoder a-jsonb_bytes cont =- getAllBytes >>= trimBytes >>= parseJSON- where- getAllBytes =- BinaryParser.remainders- trimBytes =- maybe (BinaryParser.failure "Empty input") return .- fmap snd . ByteString.uncons- parseJSON =- either BinaryParser.failure return . cont----- ** Textual------------------------------ |--- A UTF-8-decoded char.-{-# INLINABLE char #-}-char :: Decoder Char-char =- fmap Text.uncons text_strict >>= \case- Just (c, "") -> return c- Nothing -> failure "Empty input"- _ -> failure "Consumed too much"---- |--- Any of the variable-length character types:--- BPCHAR, VARCHAR, NAME and TEXT.-{-# INLINABLE text_strict #-}-text_strict :: Decoder Text-text_strict =- remainders >>= either (failure . exception) return . Text.decodeUtf8'- where- exception =- \case- Text.DecodeError message byte -> fromString message- _ -> $bug "Unexpected unicode exception"---- |--- Any of the variable-length character types:--- BPCHAR, VARCHAR, NAME and TEXT.-{-# INLINABLE text_lazy #-}-text_lazy :: Decoder LazyText-text_lazy =- bytea_lazy >>= either (failure . exception) return . LazyText.decodeUtf8'- where- exception =- \case- Text.DecodeError message byte -> fromString message- _ -> $bug "Unexpected unicode exception"---- |--- BYTEA or any other type in its undecoded form.-{-# INLINE bytea_strict #-}-bytea_strict :: Decoder ByteString-bytea_strict =- remainders---- |--- BYTEA or any other type in its undecoded form.-{-# INLINE bytea_lazy #-}-bytea_lazy :: Decoder LazyByteString-bytea_lazy =- fmap LazyByteString.fromStrict remainders----- * Date and Time------------------------------ |--- @DATE@ values decoding.-date :: Decoder Day-date =- fmap (Time.postgresJulianToDay . fromIntegral) (int :: Decoder Int32)---- |--- @TIME@ values decoding for servers, which have @integer_datetimes@ enabled.-time_int :: Decoder TimeOfDay-time_int =- fmap Time.microsToTimeOfDay int---- |--- @TIME@ values decoding for servers, which don't have @integer_datetimes@ enabled.-time_float :: Decoder TimeOfDay-time_float =- fmap Time.secsToTimeOfDay float8---- |--- @TIMETZ@ values decoding for servers, which have @integer_datetimes@ enabled.-timetz_int :: Decoder (TimeOfDay, TimeZone)-timetz_int =- (,) <$> sized 8 time_int <*> tz---- |--- @TIMETZ@ values decoding for servers, which don't have @integer_datetimes@ enabled.-timetz_float :: Decoder (TimeOfDay, TimeZone)-timetz_float =- (,) <$> sized 8 time_float <*> tz--{-# INLINE tz #-}-tz :: Decoder TimeZone-tz =- fmap (minutesToTimeZone . negate . (flip div 60) . fromIntegral) (int :: Decoder Int32)---- |--- @TIMESTAMP@ values decoding for servers, which have @integer_datetimes@ enabled.-timestamp_int :: Decoder LocalTime-timestamp_int =- fmap Time.microsToLocalTime int---- |--- @TIMESTAMP@ values decoding for servers, which don't have @integer_datetimes@ enabled.-timestamp_float :: Decoder LocalTime-timestamp_float =- fmap Time.secsToLocalTime float8---- |--- @TIMESTAMP@ values decoding for servers, which have @integer_datetimes@ enabled.-timestamptz_int :: Decoder UTCTime-timestamptz_int =- fmap Time.microsToUTC int---- |--- @TIMESTAMP@ values decoding for servers, which don't have @integer_datetimes@ enabled.-timestamptz_float :: Decoder UTCTime-timestamptz_float =- fmap Time.secsToUTC float8---- |--- @INTERVAL@ values decoding for servers, which don't have @integer_datetimes@ enabled.-interval_int :: Decoder DiffTime-interval_int =- do- u <- sized 8 int- d <- sized 4 int- m <- int- return $ Interval.toDiffTime $ Interval.Interval u d m---- |--- @INTERVAL@ values decoding for servers, which have @integer_datetimes@ enabled.-interval_float :: Decoder DiffTime-interval_float =- do- u <- sized 8 (fmap (round . (*(10^6)) . toRational) float8)- d <- sized 4 int- m <- int- return $ Interval.toDiffTime $ Interval.Interval u d m----- * Exotic------------------------------ |--- A lower-level array data parser,--- which aggregates the intermediate data representation as per the Postgres format.--- --- Only use this if 'array' doesn't fit your case.-{-# INLINABLE arrayRep #-}-arrayRep :: Decoder Data.Array-arrayRep =- do- dimensionsAmount <- intOfSize 4- nullsValue <- nulls- oid <- intOfSize 4- dimensions <- Vector.replicateM dimensionsAmount dimension- let valuesAmount = (Vector.product . Vector.map fst) dimensions- values <- Vector.replicateM (fromIntegral valuesAmount) content- return (dimensions, values, nullsValue, oid)- where- dimension =- (,) <$> intOfSize 4 <*> intOfSize 4- nulls =- intOfSize 4 >>= \(x :: Word32) -> case x of- 0 -> return False- 1 -> return True- w -> failure $ "Invalid value: " <> (fromString . show) w--{-# INLINABLE compositeRep #-}-compositeRep :: Decoder Data.Composite-compositeRep =- do- componentsAmount <- intOfSize 4- Vector.replicateM componentsAmount component- where- component =- (,) <$> intOfSize 4 <*> content---- |--- A function for generic in place parsing of an HStore value.--- --- Accepts:--- --- * An implementation of the @replicateM@ function--- (@Control.Monad.'Control.Monad.replicateM'@, @Data.Vector.'Data.Vector.replicateM'@),--- which determines how to produce the final datastructure from the rows.--- --- * A decoder for keys.--- --- * A decoder for values.--- --- Here's how you can use it to produce a parser to list:--- --- @--- hstoreAsList :: Decoder [ ( Text , Maybe Text ) ]--- hstoreAsList =--- hstore replicateM text text--- @--- -{-# INLINABLE hstore #-}-hstore :: ( forall m. Monad m => Int -> m ( k , Maybe v ) -> m r ) -> Decoder k -> Decoder v -> Decoder r-hstore replicateM keyContent valueContent =- do- componentsAmount <- intOfSize 4- replicateM componentsAmount component- where- component =- (,) <$> key <*> value- where- key =- onContent keyContent >>= nonNull- value =- onContent valueContent--{-# INLINABLE hstoreRep #-}-hstoreRep :: Decoder Data.HStore-hstoreRep =- do- componentsAmount <- intOfSize 4- Vector.replicateM componentsAmount component- where- component =- (,) <$> key <*> content- where- key =- intOfSize 4 >>= bytesOfSize----- * Composite----------------------------newtype CompositeDecoder a =- CompositeDecoder ( Decoder a )- deriving ( Functor , Applicative , Monad )---- |--- Unlift a 'CompositeDecoder' to a value 'Decoder'.-{-# INLINE composite #-}-composite :: CompositeDecoder a -> Decoder a-composite (CompositeDecoder decoder) =- numOfComponents *> decoder- where- numOfComponents =- unitOfSize 4---- |--- Lift a value 'Decoder' into 'CompositeDecoder'.-{-# INLINE compositeValue #-}-compositeValue :: Decoder a -> CompositeDecoder ( Maybe a )-compositeValue valueDecoder =- CompositeDecoder (skipOid *> onContent valueDecoder)- where- skipOid =- unitOfSize 4---- |--- Lift a non-nullable value 'Decoder' into 'CompositeDecoder'.-{-# INLINE compositeNonNullValue #-}-compositeNonNullValue :: Decoder a -> CompositeDecoder a-compositeNonNullValue valueDecoder =- CompositeDecoder (skipOid *> onContent valueDecoder >>= maybe (failure "Unexpected NULL") return)- where- skipOid =- unitOfSize 4----- * Array------------------------------ |--- An efficient generic array decoder,--- which constructs the result value in place while parsing.--- --- Here's how you can use it to produce a specific array value decoder:--- --- @--- x :: Decoder [ [ Text ] ]--- x =--- array (arrayDimension replicateM (fmap catMaybes (arrayDimension replicateM (arrayValue text))))--- @--- -newtype ArrayDecoder a =- ArrayDecoder ( [ Word32 ] -> Decoder a )- deriving ( Functor )---- |--- Unlift an 'ArrayDecoder' to a value 'Decoder'.-{-# INLINE array #-}-array :: ArrayDecoder a -> Decoder a-array (ArrayDecoder decoder) =- do- dimensionsAmount <- intOfSize 4- if dimensionsAmount /= 0- then do- unitOfSize (4 + 4)- dimensionSizes <- replicateM dimensionsAmount dimensionSize- decoder dimensionSizes- else decoder [0]- where- dimensionSize =- intOfSize 4 <* unitOfSize 4---- |--- A function for parsing a dimension of an array.--- Provides support for multi-dimensional arrays.--- --- Accepts:--- --- * An implementation of the @replicateM@ function--- (@Control.Monad.'Control.Monad.replicateM'@, @Data.Vector.'Data.Vector.replicateM'@),--- which determines the output value.--- --- * A decoder of its components, which can be either another 'arrayDimension' or 'arrayValue'.--- -{-# INLINE arrayDimension #-}-arrayDimension :: ( forall m. Monad m => Int -> m a -> m b ) -> ArrayDecoder a -> ArrayDecoder b-arrayDimension replicateM (ArrayDecoder component) =- ArrayDecoder $ \case- head : tail -> replicateM (fromIntegral head) (component tail)- _ -> failure "A missing dimension length"---- |--- Lift a value 'Decoder' into 'ArrayDecoder' for parsing of nullable leaf values.-{-# INLINE arrayValue #-}-arrayValue :: Decoder a -> ArrayDecoder ( Maybe a )-arrayValue =- ArrayDecoder . const . onContent---- |--- Lift a value 'Decoder' into 'ArrayDecoder' for parsing of non-nullable leaf values.-{-# INLINE arrayNonNullValue #-}-arrayNonNullValue :: Decoder a -> ArrayDecoder a-arrayNonNullValue =- ArrayDecoder . const . join . fmap (maybe (failure "Unexpected NULL") return) . onContent----- * Enum------------------------------ |--- Given a partial mapping from text to value,--- produces a decoder of that value.-{-# INLINE enum #-}-enum :: (Text -> Maybe a) -> Decoder a-enum mapping =- text_strict >>= onText- where- onText text =- maybe onNothing onJust (mapping text)- where- onNothing =- failure ("No mapping for text \"" <> text <> "\"")- onJust =- pure
+ library/PostgreSQL/Binary/Decoding.hs view
@@ -0,0 +1,535 @@+module PostgreSQL.Binary.Decoding+(+ valueParser,+ -- + Value,+ -- * Primitive+ int,+ float4,+ float8,+ bool,+ bytea_strict,+ bytea_lazy,+ -- * Textual+ text_strict,+ text_lazy,+ char,+ -- * Misc+ fn,+ numeric,+ uuid,+ inet,+ json_ast,+ json_bytes,+ jsonb_ast,+ jsonb_bytes,+ -- * Time+ date,+ time_int,+ time_float,+ timetz_int,+ timetz_float,+ timestamp_int,+ timestamp_float,+ timestamptz_int,+ timestamptz_float,+ interval_int,+ interval_float,+ -- * Exotic+ -- ** Array+ Array,+ array,+ valueArray,+ nullableValueArray,+ dimensionArray,+ -- ** Composite+ Composite,+ composite,+ valueComposite,+ nullableValueComposite,+ -- ** HStore+ hstore,+ -- **+ enum,+)+where++import PostgreSQL.Binary.Prelude hiding (take, bool, drop, state, fail, failure)+import BinaryParser+import qualified PostgreSQL.Binary.Integral as Integral+import qualified PostgreSQL.Binary.Interval as Interval+import qualified PostgreSQL.Binary.Numeric as Numeric+import qualified PostgreSQL.Binary.Time as Time+import qualified PostgreSQL.Binary.Inet as Inet+import qualified Data.Vector as Vector+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.Encoding.Error as Text+import qualified Data.Text.Lazy.Encoding as LazyText+import qualified Data.UUID as UUID+import qualified Data.Aeson as Aeson+import qualified Network.IP.Addr as IPAddr+++type Value =+ BinaryParser++valueParser :: Value a -> ByteString -> Either Text a+valueParser =+ BinaryParser.run++-- * Helpers+-------------------------++-- |+-- Any int number of a limited byte-size.+{-# INLINE intOfSize #-}+intOfSize :: (Integral a, Bits a) => Int -> Value a+intOfSize x =+ fmap Integral.pack (bytesOfSize x)++{-# INLINABLE onContent #-}+onContent :: Value a -> Value ( Maybe a )+onContent decoder =+ size >>=+ \case+ (-1) -> pure Nothing+ n -> fmap Just (sized (fromIntegral n) decoder)+ where+ size =+ intOfSize 4 :: Value Int32++{-# INLINABLE content #-}+content :: Value (Maybe ByteString)+content =+ intOfSize 4 >>= \case+ (-1) -> pure Nothing+ n -> fmap Just (bytesOfSize n)++{-# INLINE nonNull #-}+nonNull :: Maybe a -> Value a+nonNull =+ maybe (failure "Unexpected NULL") return+++-- * Primitive+-------------------------++-- |+-- Lifts a custom decoder implementation.+{-# INLINE fn #-}+fn :: (ByteString -> Either Text a) -> Value a+fn fn =+ BinaryParser.remainders >>= either BinaryParser.failure return . fn++{-# INLINE int #-}+int :: (Integral a, Bits a) => Value a+int =+ fmap Integral.pack remainders++float4 :: Value Float+float4 =+ unsafeCoerce (int :: Value Int32)++float8 :: Value Double+float8 =+ unsafeCoerce (int :: Value Int64)++{-# INLINE bool #-}+bool :: Value Bool+bool =+ fmap (== 1) byte++{-# NOINLINE numeric #-}+numeric :: Value Scientific+numeric =+ do+ componentsAmount <- intOfSize 2+ pointIndex <- intOfSize 2+ signCode <- intOfSize 2+ unitOfSize 2+ components <- Vector.replicateM componentsAmount (intOfSize 2)+ either failure return (Numeric.scientific pointIndex signCode components)++{-# INLINABLE uuid #-}+uuid :: Value UUID+uuid =+ UUID.fromWords <$> intOfSize 4 <*> intOfSize 4 <*> intOfSize 4 <*> intOfSize 4++{-# INLINE ip4 #-}+ip4 :: Value IPAddr.IP4+ip4 =+ IPAddr.ip4FromOctets <$> intOfSize 1 <*> intOfSize 1 <*> intOfSize 1 <*> intOfSize 1++{-# INLINE ip6 #-}+ip6 :: Value IPAddr.IP6+ip6 =+ IPAddr.ip6FromWords <$> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2 <*> intOfSize 2++{-# INLINABLE inet #-}+inet :: Value (IPAddr.NetAddr IPAddr.IP)+inet = do+ af <- intOfSize 1+ netmask <- intOfSize 1+ isCidr <- intOfSize 1+ ipSize <- intOfSize 1+ if | af == Inet.inetAddressFamily ->+ do ip <- ip4+ return $ inetFromBytes af netmask isCidr ipSize (IPAddr.IPv4 ip)+ | af == Inet.inet6AddressFamily ->+ do ip <- ip6+ return $ inetFromBytes af netmask isCidr ipSize (IPAddr.IPv6 ip)+ | otherwise -> BinaryParser.failure ("Unknown address family: " <> fromString (show af))+ where+ inetFromBytes :: Word8 -> Word8 -> Word8 -> Int8 -> IPAddr.IP -> IPAddr.NetAddr IPAddr.IP+ inetFromBytes _ netmask _ _ ip = IPAddr.netAddr ip netmask++{-# INLINABLE json_ast #-}+json_ast :: Value Aeson.Value+json_ast =+ bytea_strict >>= either (BinaryParser.failure . fromString) pure . Aeson.eitherDecodeStrict'++-- |+-- Given a function, which parses a plain UTF-8 JSON string encoded as a byte-array,+-- produces a decoder.+{-# INLINABLE json_bytes #-}+json_bytes :: (ByteString -> Either Text a) -> Value a+json_bytes cont =+ getAllBytes >>= parseJSON+ where+ getAllBytes =+ BinaryParser.remainders+ parseJSON =+ either BinaryParser.failure return . cont++{-# INLINABLE jsonb_ast #-}+jsonb_ast :: Value Aeson.Value+jsonb_ast =+ jsonb_bytes $ mapLeft fromString . Aeson.eitherDecodeStrict'++-- |+-- Given a function, which parses a plain UTF-8 JSON string encoded as a byte-array,+-- produces a decoder.+-- +-- For those wondering, yes,+-- JSONB is encoded as plain JSON string in the binary format of Postgres.+-- Sad, but true.+{-# INLINABLE jsonb_bytes #-}+jsonb_bytes :: (ByteString -> Either Text a) -> Value a+jsonb_bytes cont =+ getAllBytes >>= trimBytes >>= parseJSON+ where+ getAllBytes =+ BinaryParser.remainders+ trimBytes =+ maybe (BinaryParser.failure "Empty input") return .+ fmap snd . ByteString.uncons+ parseJSON =+ either BinaryParser.failure return . cont+++-- ** Textual+-------------------------++-- |+-- A UTF-8-decoded char.+{-# INLINABLE char #-}+char :: Value Char+char =+ fmap Text.uncons text_strict >>= \case+ Just (c, "") -> return c+ Nothing -> failure "Empty input"+ _ -> failure "Consumed too much"++-- |+-- Any of the variable-length character types:+-- BPCHAR, VARCHAR, NAME and TEXT.+{-# INLINABLE text_strict #-}+text_strict :: Value Text+text_strict =+ remainders >>= either (failure . exception) return . Text.decodeUtf8'+ where+ exception =+ \case+ Text.DecodeError message byte -> fromString message+ _ -> $bug "Unexpected unicode exception"++-- |+-- Any of the variable-length character types:+-- BPCHAR, VARCHAR, NAME and TEXT.+{-# INLINABLE text_lazy #-}+text_lazy :: Value LazyText+text_lazy =+ bytea_lazy >>= either (failure . exception) return . LazyText.decodeUtf8'+ where+ exception =+ \case+ Text.DecodeError message byte -> fromString message+ _ -> $bug "Unexpected unicode exception"++-- |+-- BYTEA or any other type in its undecoded form.+{-# INLINE bytea_strict #-}+bytea_strict :: Value ByteString+bytea_strict =+ remainders++-- |+-- BYTEA or any other type in its undecoded form.+{-# INLINE bytea_lazy #-}+bytea_lazy :: Value LazyByteString+bytea_lazy =+ fmap LazyByteString.fromStrict remainders+++-- * Date and Time+-------------------------++-- |+-- @DATE@ values decoding.+date :: Value Day+date =+ fmap (Time.postgresJulianToDay . fromIntegral) (int :: Value Int32)++-- |+-- @TIME@ values decoding for servers, which have @integer_datetimes@ enabled.+time_int :: Value TimeOfDay+time_int =+ fmap Time.microsToTimeOfDay int++-- |+-- @TIME@ values decoding for servers, which don't have @integer_datetimes@ enabled.+time_float :: Value TimeOfDay+time_float =+ fmap Time.secsToTimeOfDay float8++-- |+-- @TIMETZ@ values decoding for servers, which have @integer_datetimes@ enabled.+timetz_int :: Value (TimeOfDay, TimeZone)+timetz_int =+ (,) <$> sized 8 time_int <*> tz++-- |+-- @TIMETZ@ values decoding for servers, which don't have @integer_datetimes@ enabled.+timetz_float :: Value (TimeOfDay, TimeZone)+timetz_float =+ (,) <$> sized 8 time_float <*> tz++{-# INLINE tz #-}+tz :: Value TimeZone+tz =+ fmap (minutesToTimeZone . negate . (flip div 60) . fromIntegral) (int :: Value Int32)++-- |+-- @TIMESTAMP@ values decoding for servers, which have @integer_datetimes@ enabled.+timestamp_int :: Value LocalTime+timestamp_int =+ fmap Time.microsToLocalTime int++-- |+-- @TIMESTAMP@ values decoding for servers, which don't have @integer_datetimes@ enabled.+timestamp_float :: Value LocalTime+timestamp_float =+ fmap Time.secsToLocalTime float8++-- |+-- @TIMESTAMP@ values decoding for servers, which have @integer_datetimes@ enabled.+timestamptz_int :: Value UTCTime+timestamptz_int =+ fmap Time.microsToUTC int++-- |+-- @TIMESTAMP@ values decoding for servers, which don't have @integer_datetimes@ enabled.+timestamptz_float :: Value UTCTime+timestamptz_float =+ fmap Time.secsToUTC float8++-- |+-- @INTERVAL@ values decoding for servers, which don't have @integer_datetimes@ enabled.+interval_int :: Value DiffTime+interval_int =+ do+ u <- sized 8 int+ d <- sized 4 int+ m <- int+ return $ Interval.toDiffTime $ Interval.Interval u d m++-- |+-- @INTERVAL@ values decoding for servers, which have @integer_datetimes@ enabled.+interval_float :: Value DiffTime+interval_float =+ do+ u <- sized 8 (fmap (round . (*(10^6)) . toRational) float8)+ d <- sized 4 int+ m <- int+ return $ Interval.toDiffTime $ Interval.Interval u d m+++-- * Exotic+-------------------------++-- |+-- A function for generic in place parsing of an HStore value.+-- +-- Accepts:+-- +-- * An implementation of the @replicateM@ function+-- (@Control.Monad.'Control.Monad.replicateM'@, @Data.Vector.'Data.Vector.replicateM'@),+-- which determines how to produce the final datastructure from the rows.+-- +-- * A decoder for keys.+-- +-- * A decoder for values.+-- +-- Here's how you can use it to produce a parser to list:+-- +-- @+-- hstoreAsList :: Value [ ( Text , Maybe Text ) ]+-- hstoreAsList =+-- hstore replicateM text text+-- @+-- +{-# INLINABLE hstore #-}+hstore :: ( forall m. Monad m => Int -> m ( k , Maybe v ) -> m r ) -> Value k -> Value v -> Value r+hstore replicateM keyContent valueContent =+ do+ componentsAmount <- intOfSize 4+ replicateM componentsAmount component+ where+ component =+ (,) <$> key <*> value+ where+ key =+ onContent keyContent >>= nonNull+ value =+ onContent valueContent+++-- * Composite+-------------------------++newtype Composite a =+ Composite ( Value a )+ deriving ( Functor , Applicative , Monad )++-- |+-- Unlift a 'Composite' to a value 'Value'.+{-# INLINE composite #-}+composite :: Composite a -> Value a+composite (Composite decoder) =+ numOfComponents *> decoder+ where+ numOfComponents =+ unitOfSize 4++-- |+-- Lift a value 'Value' into 'Composite'.+{-# INLINE nullableValueComposite #-}+nullableValueComposite :: Value a -> Composite ( Maybe a )+nullableValueComposite valueValue =+ Composite (skipOid *> onContent valueValue)+ where+ skipOid =+ unitOfSize 4++-- |+-- Lift a non-nullable value 'Value' into 'Composite'.+{-# INLINE valueComposite #-}+valueComposite :: Value a -> Composite a+valueComposite valueValue =+ Composite (skipOid *> onContent valueValue >>= maybe (failure "Unexpected NULL") return)+ where+ skipOid =+ unitOfSize 4+++-- * Array+-------------------------++-- |+-- An efficient generic array decoder,+-- which constructs the result value in place while parsing.+-- +-- Here's how you can use it to produce a specific array value decoder:+-- +-- @+-- x :: Value [ [ Text ] ]+-- x =+-- array (dimensionArray replicateM (fmap catMaybes (dimensionArray replicateM (nullableValueArray text))))+-- @+-- +newtype Array a =+ Array ( [ Word32 ] -> Value a )+ deriving ( Functor )++-- |+-- Unlift an 'Array' to a value 'Value'.+{-# INLINE array #-}+array :: Array a -> Value a+array (Array decoder) =+ do+ dimensionsAmount <- intOfSize 4+ if dimensionsAmount /= 0+ then do+ unitOfSize (4 + 4)+ dimensionSizes <- replicateM dimensionsAmount dimensionSize+ decoder dimensionSizes+ else decoder [0]+ where+ dimensionSize =+ intOfSize 4 <* unitOfSize 4++-- |+-- A function for parsing a dimension of an array.+-- Provides support for multi-dimensional arrays.+-- +-- Accepts:+-- +-- * An implementation of the @replicateM@ function+-- (@Control.Monad.'Control.Monad.replicateM'@, @Data.Vector.'Data.Vector.replicateM'@),+-- which determines the output value.+-- +-- * A decoder of its components, which can be either another 'dimensionArray' or 'nullableValueArray'.+-- +{-# INLINE dimensionArray #-}+dimensionArray :: ( forall m. Monad m => Int -> m a -> m b ) -> Array a -> Array b+dimensionArray replicateM (Array component) =+ Array $ \case+ head : tail -> replicateM (fromIntegral head) (component tail)+ _ -> failure "A missing dimension length"++-- |+-- Lift a value 'Value' into 'Array' for parsing of nullable leaf values.+{-# INLINE nullableValueArray #-}+nullableValueArray :: Value a -> Array ( Maybe a )+nullableValueArray =+ Array . const . onContent++-- |+-- Lift a value 'Value' into 'Array' for parsing of non-nullable leaf values.+{-# INLINE valueArray #-}+valueArray :: Value a -> Array a+valueArray =+ Array . const . join . fmap (maybe (failure "Unexpected NULL") return) . onContent+++-- * Enum+-------------------------++-- |+-- Given a partial mapping from text to value,+-- produces a decoder of that value.+{-# INLINE enum #-}+enum :: (Text -> Maybe a) -> Value a+enum mapping =+ text_strict >>= onText+ where+ onText text =+ maybe onNothing onJust (mapping text)+ where+ onNothing =+ failure ("No mapping for text \"" <> text <> "\"")+ onJust =+ pure
− library/PostgreSQL/Binary/Encoder.hs
@@ -1,576 +0,0 @@-{-# LANGUAGE CPP #-}-module PostgreSQL.Binary.Encoder-(- run,- -- * Value encoder- Encoder,- int2_int16,- int2_word16,- int4_int32,- int4_word32,- int8_int64,- int8_word64,- float4,- float8,- composite,- bool,- numeric,- uuid,- inet,- json_ast,- json_bytes,- jsonb_ast,- jsonb_bytes,- 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,- -- * Array encoder- ArrayEncoder,- arrayValue,- arrayNullableValue,- arrayDimension,- arrayRep,- -- * Enum- 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 PostgreSQL.Binary.Inet as Inet-import qualified Control.Foldl as Foldl-import qualified Network.IP.Addr as IPAddr---type Encoder a =- a -> Builder--{-# INLINE run #-}-run :: Encoder a -> a -> ByteString-run encoder =- LazyByteString.toStrict . Builder.toLazyByteString . encoder--{-# INLINE tuple2 #-}-tuple2 :: Encoder a -> Encoder b -> Encoder (a, b)-tuple2 e1 e2 =- \(v1, v2) -> e1 v1 <> e2 v2--{-# INLINE tuple3 #-}-tuple3 :: Encoder a -> Encoder b -> Encoder c -> Encoder (a, b, c)-tuple3 e1 e2 e3 =- \(v1, v2, v3) -> e1 v1 <> e2 v2 <> e3 v3--{-# INLINE tuple4 #-}-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--{-# INLINE tuple5 #-}-tuple5 :: Encoder a -> Encoder b -> Encoder c -> Encoder d -> Encoder e -> Encoder (a, b, c, d, e)-tuple5 e1 e2 e3 e4 e5 (v1, v2, v3, v4, v5) = e1 v1 <> e2 v2 <> e3 v3 <> e4 v4 <> e5 v5--{-# INLINE tuple8 #-}-tuple8 :: Encoder a -> Encoder b -> Encoder c -> Encoder d -> Encoder e -> Encoder f -> Encoder g -> Encoder h -> Encoder (a, b, c, d, e, f, g, h)-tuple8 e1 e2 e3 e4 e5 e6 e7 e8 (v1, v2, v3, v4, v5, v6, v7, v8) = e1 v1 <> e2 v2 <> e3 v3 <> e4 v4 <> e5 v5 <> e6 v6 <> e7 v7 <> e8 v8--{-# INLINE premap #-}-premap :: (a -> b) -> Encoder b -> Encoder a-premap f e =- e . f--{-# INLINE int_int8 #-}-int_int8 :: Encoder Int8-int_int8 =- Builder.int8--{-# INLINE int_word8 #-}-int_word8 :: Encoder Word8-int_word8 =- Builder.word8--{-# INLINE int2_int16 #-}-int2_int16 :: Encoder Int16-int2_int16 =- Builder.int16BE--{-# INLINE int2_word16 #-}-int2_word16 :: Encoder Word16-int2_word16 =- Builder.word16BE--{-# INLINE int4_int32 #-}-int4_int32 :: Encoder Int32-int4_int32 =- Builder.int32BE--{-# INLINE int4_word32 #-}-int4_word32 :: Encoder Word32-int4_word32 =- Builder.word32BE--{-# INLINE int4_int #-}-int4_int :: Encoder Int-int4_int =- int4_int32 . fromIntegral--{-# INLINE int8_int64 #-}-int8_int64 :: Encoder Int64-int8_int64 =- Builder.int64BE--{-# INLINE int8_word64 #-}-int8_word64 :: Encoder Word64-int8_word64 =- Builder.word64BE--{-# INLINE float4 #-}-float4 :: Encoder Float-float4 =- int4_int32 . unsafeCoerce--{-# INLINE float8 #-}-float8 :: Encoder Double-float8 =- int8_int64 . unsafeCoerce--{-# INLINE null4 #-}-null4 :: ByteStringBuilder-null4 =- Builder.string7 "\255\255\255\255"--{-# INLINABLE composite #-}-composite :: Encoder Data.Composite-composite vector =- int4_int (Vector.length vector) <>- foldMap component vector- where- component (oid, theContent) =- int4_word32 oid <> content theContent--{-# INLINABLE content #-}-content :: Encoder Data.Content-content =- \case- Nothing ->- null4- Just content ->- int4_int (ByteString.length content) <>- Builder.byteString content--{-# INLINABLE maybe #-}-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--{-# INLINE bool #-}-bool :: Encoder Bool-bool =- \case- True -> Builder.word8 1- False -> Builder.word8 0--{-# INLINABLE numeric #-}-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--{-# INLINABLE uuid #-}-uuid :: Encoder UUID-uuid =- premap UUID.toWords (tuple4 int4_word32 int4_word32 int4_word32 int4_word32)--{-# INLINABLE ip4 #-}-ip4 :: Encoder IPAddr.IP4-ip4 =- premap IPAddr.ip4ToOctets (tuple4 int_word8 int_word8 int_word8 int_word8)--{-# INLINABLE ip6 #-}-ip6 :: Encoder IPAddr.IP6-ip6 =- premap IPAddr.ip6ToWords (tuple8 int2_word16 int2_word16 int2_word16 int2_word16 int2_word16 int2_word16 int2_word16 int2_word16)--{-# INLINABLE inet #-}-inet :: Encoder (IPAddr.NetAddr IPAddr.IP)-inet i =- case IPAddr.netHost i of- IPAddr.IPv4 x -> inetAddressFamily <> netLength <> isCidr <> ip4Size <> ip4 x- IPAddr.IPv6 x -> inet6AddressFamily <> netLength <> isCidr <> ip6Size <> ip6 x- where- inetAddressFamily =- int_word8 Inet.inetAddressFamily- inet6AddressFamily =- int_word8 Inet.inet6AddressFamily- netLength =- int_word8 (IPAddr.netLength i)- isCidr =- int_word8 0- ip4Size =- int_int8 4- ip6Size =- int_int8 16--{-# INLINABLE json_ast #-}-json_ast :: Encoder Aeson.Value-#if MIN_VERSION_aeson(0,10,0)-json_ast =- Aeson.fromEncoding . Aeson.toEncoding-#else-json_ast =- Builder.lazyByteString . Aeson.encode-#endif--{-# INLINABLE json_bytes #-}-json_bytes :: Encoder ByteString-json_bytes =- Builder.byteString--{-# INLINABLE jsonb_ast #-}-jsonb_ast :: Encoder Aeson.Value-jsonb_ast =- \x -> "\1" <> json_ast x--{-# INLINABLE jsonb_bytes #-}-jsonb_bytes :: Encoder ByteString-jsonb_bytes =- \x -> "\1" <> Builder.byteString x---- * Text------------------------------ |--- A UTF-8-encoded char.--- --- Note that since it's UTF-8-encoded--- not the \"char\" but the \"text\" OID should be used with it.-{-# INLINABLE char #-}-char :: Encoder Char-char = - Builder.charUtf8--{-# INLINABLE text_strict #-}-text_strict :: Encoder Text-text_strict =- Text.encodeUtf8BuilderEscaped BuilderPrim.nullByteIgnoringBoundedPrim--{-# INLINABLE text_lazy #-}-text_lazy :: Encoder LazyText.Text-text_lazy =- LazyText.encodeUtf8BuilderEscaped BuilderPrim.nullByteIgnoringBoundedPrim--{-# INLINABLE bytea_strict #-}-bytea_strict :: Encoder ByteString-bytea_strict =- Builder.byteString- -{-# INLINABLE bytea_lazy #-}-bytea_lazy :: Encoder LazyByteString.ByteString-bytea_lazy =- Builder.lazyByteString---- * Date and Time----------------------------{-# INLINABLE date #-}-date :: Encoder Day-date =- int4_int32 . fromIntegral . Time.dayToPostgresJulian--{-# INLINABLE time_int #-}-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))--{-# INLINABLE time_float #-}-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)))--{-# INLINABLE timetz_int #-}-timetz_int :: Encoder (TimeOfDay, TimeZone)-timetz_int (timeX, tzX) =- time_int timeX <> tz tzX--{-# INLINABLE timetz_float #-}-timetz_float :: Encoder (TimeOfDay, TimeZone)-timetz_float (timeX, tzX) =- time_float timeX <> tz tzX--{-# INLINE tz #-}-tz :: Encoder TimeZone-tz =- int4_int . (*60) . negate . timeZoneMinutes--{-# INLINABLE timestamp_int #-}-timestamp_int :: Encoder LocalTime-timestamp_int =- int8_int64 . Time.localTimeToMicros--{-# INLINABLE timestamp_float #-}-timestamp_float :: Encoder LocalTime-timestamp_float =- float8 . Time.localTimeToSecs--{-# INLINABLE timestamptz_int #-}-timestamptz_int :: Encoder UTCTime-timestamptz_int =- int8_int64 . Time.utcToMicros--{-# INLINABLE timestamptz_float #-}-timestamptz_float :: Encoder UTCTime-timestamptz_float =- float8 . Time.utcToSecs--{-# INLINABLE interval_int #-}-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--{-# INLINABLE interval_float #-}-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)----- * Array----------------------------newtype ArrayEncoder a =- ArrayEncoder (a -> (Builder, [Int32], Bool))--{-# INLINABLE array #-}-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--{-# INLINABLE arrayValue #-}-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)--{-# INLINABLE arrayNullableValue #-}-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)--{-# INLINABLE arrayDimension #-}-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)- ---- * Array rep----------------------------{-# INLINABLE arrayRep #-}-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------------------------------ |--- A polymorphic in-place @HSTORE@ encoder.--- --- Accepts:--- --- * An implementation of the @foldl@ function--- (e.g., @Data.Foldable.'foldl''@),--- which determines the input value.--- --- Here's how you can use it to produce a specific encoder:--- --- @--- hashMapHStore :: Encoder (Data.HashMap.Strict.HashMap Text (Maybe Text))--- hashMapHStore =--- hstore foldl'--- @--- -{-# INLINABLE hstore #-}-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--{-# INLINABLE hstoreRep #-}-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------------------------------ |--- Given a function,--- which maps the value into the textual enum label from the DB side,--- produces an encoder of that value--- -{-# INLINE enum #-}-enum :: (a -> Text) -> Encoder a-enum asText =- text_strict . asText
+ library/PostgreSQL/Binary/Encoding.hs view
@@ -0,0 +1,347 @@+module PostgreSQL.Binary.Encoding+(+ -- * Encoding+ Encoding,+ encodingBytes,++ -- * + array,+ array_foldable,+ array_vector,+ nullableArray_vector,+ hStore_foldable,+ hStore_hashMap,+ hStore_map,++ -- * Primitives+ bool,+ int2_int16,+ int2_word16,+ int4_int32,+ int4_word32,+ int8_int64,+ int8_word64,+ float4,+ float8,+ numeric,+ uuid,+ inet,+ char_utf8,+ text_strict,+ text_lazy,+ bytea_strict,+ bytea_lazy,+ -- ** Time+ -- | Some of the functions in this section are distinguished based+ -- on the @integer_datetimes@ setting of the server.+ date,+ time_int,+ time_float,+ timetz_int,+ timetz_float,+ timestamp_int,+ timestamp_float,+ timestamptz_int,+ timestamptz_float,+ interval_int,+ interval_float,+ -- ** JSON+ json_bytes,+ json_ast,+ jsonb_bytes,+ jsonb_ast,++ -- * Array+ Array,+ encodingArray,+ nullArray,+ dimensionArray,+)+where++import PostgreSQL.Binary.Prelude hiding (bool, length)+import qualified ByteString.StrictBuilder as C+import qualified Data.Vector as A+import qualified PostgreSQL.Binary.Encoding.Builders as B+import qualified Data.ByteString.Builder as M+import qualified Data.ByteString.Lazy as N+import qualified Data.Text.Lazy as L+import qualified Data.Aeson as R+import qualified Network.IP.Addr as G+++type Encoding =+ C.Builder++{-# INLINE encodingBytes #-}+encodingBytes :: Encoding -> ByteString+encodingBytes =+ C.builderBytes+++-- * Values+-------------------------++{-|+Turn an array builder into final value.+The first parameter is OID of the element type.+-}+{-# INLINE array #-}+array :: Word32 -> Array -> Encoding+array oid (Array payload dimensions nulls) =+ B.array oid dimensions nulls payload++{-|+A helper for encoding of arrays of single dimension from foldables.+The first parameter is OID of the element type.+-}+{-# INLINE array_foldable #-}+array_foldable :: Foldable foldable => Word32 -> (element -> Maybe Encoding) -> foldable element -> Encoding+array_foldable oid elementBuilder =+ array oid . dimensionArray foldl' (maybe nullArray encodingArray . elementBuilder)++{-|+A helper for encoding of arrays of single dimension from vectors.+The first parameter is OID of the element type.+-}+{-# INLINE array_vector #-}+array_vector :: Word32 -> (element -> Encoding) -> Vector element -> Encoding+array_vector oid elementBuilder vector =+ B.array_vector oid elementBuilder vector++{-|+A helper for encoding of arrays of single dimension from vectors.+The first parameter is OID of the element type.+-}+{-# INLINE nullableArray_vector #-}+nullableArray_vector :: Word32 -> (element -> Encoding) -> Vector (Maybe element) -> Encoding+nullableArray_vector oid elementBuilder vector =+ B.nullableArray_vector oid elementBuilder vector++{-|+A polymorphic @HSTORE@ encoder.+-}+{-# INLINE hStore_foldable #-}+hStore_foldable :: Foldable foldable => foldable (Text, Maybe Text) -> Encoding+hStore_foldable =+ B.hStoreUsingFoldl foldl++{-|+@HSTORE@ encoder from HashMap.+-}+{-# INLINE hStore_hashMap #-}+hStore_hashMap :: HashMap Text (Maybe Text) -> Encoding+hStore_hashMap =+ B.hStore_hashMap++{-|+@HSTORE@ encoder from Map.+-}+{-# INLINE hStore_map #-}+hStore_map :: Map Text (Maybe Text) -> Encoding+hStore_map =+ B.hStore_map+++-- * Primitive+-------------------------++{-# INLINE bool #-}+bool :: Bool -> Encoding+bool =+ B.bool++{-# INLINE int2_int16 #-}+int2_int16 :: Int16 -> Encoding+int2_int16 =+ B.int2_int16++{-# INLINE int2_word16 #-}+int2_word16 :: Word16 -> Encoding+int2_word16 =+ B.int2_word16++{-# INLINE int4_int32 #-}+int4_int32 :: Int32 -> Encoding+int4_int32 =+ B.int4_int32++{-# INLINE int4_word32 #-}+int4_word32 :: Word32 -> Encoding+int4_word32 =+ B.int4_word32++{-# INLINE int8_int64 #-}+int8_int64 :: Int64 -> Encoding+int8_int64 =+ B.int8_int64++{-# INLINE int8_word64 #-}+int8_word64 :: Word64 -> Encoding+int8_word64 =+ B.int8_word64++{-# INLINE float4 #-}+float4 :: Float -> Encoding+float4 =+ B.float4++{-# INLINE float8 #-}+float8 :: Double -> Encoding+float8 =+ B.float8++{-# INLINE numeric #-}+numeric :: Scientific -> Encoding+numeric =+ B.numeric++{-# INLINE uuid #-}+uuid :: UUID -> Encoding+uuid =+ B.uuid++{-# INLINE inet #-}+inet :: G.NetAddr G.IP -> Encoding+inet =+ B.inet++{-# INLINE char_utf8 #-}+char_utf8 :: Char -> Encoding+char_utf8 =+ B.char_utf8++{-# INLINE text_strict #-}+text_strict :: Text -> Encoding+text_strict =+ B.text_strict++{-# INLINE text_lazy #-}+text_lazy :: L.Text -> Encoding+text_lazy =+ B.text_lazy++{-# INLINE bytea_strict #-}+bytea_strict :: ByteString -> Encoding+bytea_strict =+ B.bytea_strict++{-# INLINE bytea_lazy #-}+bytea_lazy :: N.ByteString -> Encoding+bytea_lazy =+ B.bytea_lazy++{-# INLINE bytea_lazyBuilder #-}+bytea_lazyBuilder :: M.Builder -> Encoding+bytea_lazyBuilder =+ B.bytea_lazyBuilder++{-# INLINE date #-}+date :: Day -> Encoding+date =+ B.date++{-# INLINE time_int #-}+time_int :: TimeOfDay -> Encoding+time_int =+ B.time_int++{-# INLINE time_float #-}+time_float :: TimeOfDay -> Encoding+time_float =+ B.time_float++{-# INLINE timetz_int #-}+timetz_int :: (TimeOfDay, TimeZone) -> Encoding+timetz_int =+ B.timetz_int++{-# INLINE timetz_float #-}+timetz_float :: (TimeOfDay, TimeZone) -> Encoding+timetz_float =+ B.timetz_float++{-# INLINE timestamp_int #-}+timestamp_int :: LocalTime -> Encoding+timestamp_int =+ B.timestamp_int++{-# INLINE timestamp_float #-}+timestamp_float :: LocalTime -> Encoding+timestamp_float =+ B.timestamp_float++{-# INLINE timestamptz_int #-}+timestamptz_int :: UTCTime -> Encoding+timestamptz_int =+ B.timestamptz_int++{-# INLINE timestamptz_float #-}+timestamptz_float :: UTCTime -> Encoding+timestamptz_float =+ B.timestamptz_float++{-# INLINE interval_int #-}+interval_int :: DiffTime -> Encoding+interval_int =+ B.interval_int++{-# INLINE interval_float #-}+interval_float :: DiffTime -> Encoding+interval_float =+ B.interval_float++{-# INLINE json_bytes #-}+json_bytes :: ByteString -> Encoding+json_bytes =+ B.json_bytes++{-# INLINE json_ast #-}+json_ast :: R.Value -> Encoding+json_ast =+ B.json_ast++{-# INLINE jsonb_bytes #-}+jsonb_bytes :: ByteString -> Encoding+jsonb_bytes =+ B.jsonb_bytes++{-# INLINE jsonb_ast #-}+jsonb_ast :: R.Value -> Encoding+jsonb_ast =+ B.jsonb_ast+++-- * Array+-------------------------++{-|+Abstraction for encoding into multidimensional array.+-}+data Array =+ Array !Encoding ![Int32] !Bool++encodingArray :: Encoding -> Array+encodingArray value =+ Array (B.sized value) [] False++nullArray :: Array+nullArray =+ Array B.null4 [] True++dimensionArray :: (forall b. (b -> a -> b) -> b -> c -> b) -> (a -> Array) -> c -> Array+dimensionArray foldl' elementArray input =+ Array builder dimensions nulls+ where+ dimensions =+ foldedLength : foldedDimensions+ (builder, foldedDimensions, foldedLength, nulls) =+ foldl' step init input+ where+ init =+ (mempty, [], 0, False)+ step (!builder, _, !length, !nulls) element =+ (builder <> elementBuilder, elementDimensions, succ length, nulls || elementNulls)+ where+ Array elementBuilder elementDimensions elementNulls =+ elementArray element
+ library/PostgreSQL/Binary/Encoding/Builders.hs view
@@ -0,0 +1,456 @@+module PostgreSQL.Binary.Encoding.Builders+where++import PostgreSQL.Binary.Prelude hiding (bool)+import ByteString.StrictBuilder+import qualified Data.Vector as A+import qualified Data.Scientific as D+import qualified Data.UUID as E+import qualified Data.ByteString.Builder as M+import qualified Data.ByteString.Lazy as N+import qualified Data.Text.Encoding as J+import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.Encoding as K+import qualified Data.HashMap.Strict as F+import qualified Data.Map.Strict as Q+import qualified Data.Aeson as R+import qualified Network.IP.Addr as G+import qualified PostgreSQL.Binary.Prelude as B+import qualified PostgreSQL.Binary.Numeric as C+import qualified PostgreSQL.Binary.Inet as H+import qualified PostgreSQL.Binary.BuilderPrim as I+import qualified PostgreSQL.Binary.Time as O+import qualified PostgreSQL.Binary.Interval as P+++-- * Helpers+-------------------------++{-# NOINLINE null4 #-}+null4 :: Builder+null4 =+ int4_int (-1)++{-# INLINE sized #-}+sized :: Builder -> Builder+sized payload =+ int4_int (builderLength payload) <>+ payload++{-# INLINE sizedMaybe #-}+sizedMaybe :: (element -> Builder) -> Maybe element -> Builder+sizedMaybe elementBuilder =+ B.maybe null4 (sized . elementBuilder)++{-# NOINLINE true1 #-}+true1 :: Builder+true1 =+ word8 1++{-# NOINLINE false1 #-}+false1 :: Builder+false1 =+ word8 0++{-# NOINLINE true4 #-}+true4 :: Builder+true4 =+ int4_word32 1++{-# NOINLINE false4 #-}+false4 :: Builder+false4 =+ int4_word32 0+++-- * Primitives+-------------------------++{-# INLINE bool #-}+bool :: Bool -> Builder+bool =+ B.bool false1 true1++{-# INLINE int2_int16 #-}+int2_int16 :: Int16 -> Builder+int2_int16 =+ int16BE++{-# INLINE int2_word16 #-}+int2_word16 :: Word16 -> Builder+int2_word16 =+ word16BE++{-# INLINE int4_int32 #-}+int4_int32 :: Int32 -> Builder+int4_int32 =+ int32BE++{-# INLINE int4_word32 #-}+int4_word32 :: Word32 -> Builder+int4_word32 =+ word32BE++{-# INLINE int4_int #-}+int4_int :: Int -> Builder+int4_int =+ int4_int32 . fromIntegral++{-# INLINE int8_int64 #-}+int8_int64 :: Int64 -> Builder+int8_int64 =+ int64BE++{-# INLINE int8_word64 #-}+int8_word64 :: Word64 -> Builder+int8_word64 =+ word64BE++{-# INLINE float4 #-}+float4 :: Float -> Builder+float4 =+ int4_int32 . unsafeCoerce++{-# INLINE float8 #-}+float8 :: Double -> Builder+float8 =+ int8_int64 . unsafeCoerce++{-# INLINABLE numeric #-}+numeric :: Scientific -> Builder+numeric x =+ word16BE (fromIntegral componentsAmount) <>+ word16BE (fromIntegral pointIndex) <>+ signCode <>+ word16BE (fromIntegral trimmedExponent) <>+ foldMap word16BE components+ where+ componentsAmount = + length components+ coefficient =+ D.coefficient x+ exponent = + D.base10Exponent x+ components = + C.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 numericNegSignCode+ else numericPosSignCode++{-# NOINLINE numericNegSignCode #-}+numericNegSignCode :: Builder+numericNegSignCode =+ int2_word16 C.negSignCode++{-# NOINLINE numericPosSignCode #-}+numericPosSignCode :: Builder+numericPosSignCode =+ int2_word16 C.posSignCode++{-# INLINE uuid #-}+uuid :: UUID -> Builder+uuid uuid =+ case E.toWords uuid of+ (w1, w2, w3, w4) -> int4_word32 w1 <> int4_word32 w2 <> int4_word32 w3 <> int4_word32 w4++{-# INLINABLE ip4 #-}+ip4 :: G.IP4 -> Builder+ip4 x =+ case G.ip4ToOctets x of+ (w1, w2, w3, w4) -> word8 w1 <> word8 w2 <> word8 w3 <> word8 w4++{-# INLINABLE ip6 #-}+ip6 :: G.IP6 -> Builder+ip6 x =+ case G.ip6ToWords x of+ (w1, w2, w3, w4, w5, w6, w7, w8) ->+ int2_word16 w1 <> int2_word16 w2 <> int2_word16 w3 <> int2_word16 w4 <>+ int2_word16 w5 <> int2_word16 w6 <> int2_word16 w7 <> int2_word16 w8++{-# INLINABLE inet #-}+inet :: G.NetAddr G.IP -> Builder+inet i =+ case G.netHost i of+ G.IPv4 x -> inetAddressFamily <> netLength <> isCidr <> ip4Size <> ip4 x+ G.IPv6 x -> inet6AddressFamily <> netLength <> isCidr <> ip6Size <> ip6 x+ where+ netLength =+ word8 (G.netLength i)+ isCidr =+ false1++{-# NOINLINE inetAddressFamily #-}+inetAddressFamily :: Builder+inetAddressFamily =+ word8 H.inetAddressFamily++{-# NOINLINE inet6AddressFamily #-}+inet6AddressFamily :: Builder+inet6AddressFamily =+ word8 H.inet6AddressFamily++{-# NOINLINE ip4Size #-}+ip4Size :: Builder+ip4Size =+ word8 4++{-# NOINLINE ip6Size #-}+ip6Size :: Builder+ip6Size =+ word8 16+++-- * Text+-------------------------++-- |+-- A UTF-8-encoded char.+-- +-- Note that since it's UTF-8-encoded+-- not the \"char\" but the \"text\" OID should be used with it.+{-# INLINE char_utf8 #-}+char_utf8 :: Char -> Builder+char_utf8 = + utf8Char++{-# INLINE text_strict #-}+text_strict :: Text -> Builder+text_strict =+ bytea_lazyBuilder . J.encodeUtf8BuilderEscaped I.nullByteIgnoringBoundedPrim++{-# INLINE text_lazy #-}+text_lazy :: L.Text -> Builder+text_lazy =+ bytea_lazyBuilder . K.encodeUtf8BuilderEscaped I.nullByteIgnoringBoundedPrim++{-# INLINE bytea_strict #-}+bytea_strict :: ByteString -> Builder+bytea_strict =+ bytes++{-# INLINE bytea_lazy #-}+bytea_lazy :: N.ByteString -> Builder+bytea_lazy =+ lazyBytes++{-# INLINE bytea_lazyBuilder #-}+bytea_lazyBuilder :: M.Builder -> Builder+bytea_lazyBuilder =+ lazyBytes . M.toLazyByteString+++-- * Time+-------------------------++{-# INLINE date #-}+date :: Day -> Builder+date =+ int4_int32 . fromIntegral . O.dayToPostgresJulian++{-# INLINABLE time_int #-}+time_int :: TimeOfDay -> Builder+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))++{-# INLINABLE time_float #-}+time_float :: TimeOfDay -> Builder+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)))++{-# INLINE timetz_int #-}+timetz_int :: (TimeOfDay, TimeZone) -> Builder+timetz_int (timeX, tzX) =+ time_int timeX <> tz tzX++{-# INLINE timetz_float #-}+timetz_float :: (TimeOfDay, TimeZone) -> Builder+timetz_float (timeX, tzX) =+ time_float timeX <> tz tzX++{-# INLINE tz #-}+tz :: TimeZone -> Builder+tz =+ int4_int . (*60) . negate . timeZoneMinutes++{-# INLINE timestamp_int #-}+timestamp_int :: LocalTime -> Builder+timestamp_int =+ int8_int64 . O.localTimeToMicros++{-# INLINE timestamp_float #-}+timestamp_float :: LocalTime -> Builder+timestamp_float =+ float8 . O.localTimeToSecs++{-# INLINE timestamptz_int #-}+timestamptz_int :: UTCTime -> Builder+timestamptz_int =+ int8_int64 . O.utcToMicros++{-# INLINE timestamptz_float #-}+timestamptz_float :: UTCTime -> Builder+timestamptz_float =+ float8 . O.utcToSecs++{-# INLINABLE interval_int #-}+interval_int :: DiffTime -> Builder+interval_int x =+ int64BE u <>+ int32BE d <>+ int32BE m+ where+ P.Interval u d m = + fromMaybe (error ("Too large DiffTime value for an interval " <> show x)) $+ P.fromDiffTime x++{-# INLINABLE interval_float #-}+interval_float :: DiffTime -> Builder+interval_float x =+ float8 s <>+ int32BE d <>+ int32BE m+ where+ P.Interval u d m = + fromMaybe (error ("Too large DiffTime value for an interval " <> show x)) $+ P.fromDiffTime x+ s =+ fromIntegral u / (10^6)+++-- * JSON+-------------------------++{-# INLINE json_bytes #-}+json_bytes :: ByteString -> Builder+json_bytes =+ bytes++{-# INLINE json_ast #-}+json_ast :: R.Value -> Builder+json_ast =+ lazyBytes . R.encode++{-# INLINE jsonb_bytes #-}+jsonb_bytes :: ByteString -> Builder+jsonb_bytes =+ mappend "\1" . bytes++{-# INLINE jsonb_ast #-}+jsonb_ast :: R.Value -> Builder+jsonb_ast =+ mappend "\1" . json_ast+++-- * Array+-------------------------++{-# INLINE array_vector #-}+array_vector :: Word32 -> (element -> Builder) -> Vector element -> Builder+array_vector oid elementBuilder vector =+ array oid dimensions False payload+ where+ dimensions =+ [fromIntegral (A.length vector)]+ payload =+ foldMap (sized . elementBuilder) vector++{-# INLINE nullableArray_vector #-}+nullableArray_vector :: Word32 -> (element -> Builder) -> Vector (Maybe element) -> Builder+nullableArray_vector oid elementBuilder vector =+ array oid dimensions True payload+ where+ dimensions =+ [fromIntegral (A.length vector)]+ payload =+ foldMap (sizedMaybe elementBuilder) vector++{-# INLINABLE array #-}+array :: Word32 -> [Int32] -> Bool -> Builder -> Builder+array oid dimensions nulls payload =+ int4_int (B.length dimensions) <>+ B.bool false4 true4 nulls <>+ int4_word32 oid <>+ foldMap arrayDimension dimensions <>+ payload++{-# INLINE arrayDimension #-}+arrayDimension :: Int32 -> Builder+arrayDimension dimension =+ int4_int32 dimension <> true4+++-- * HStore+-------------------------++{-|+A polymorphic in-place @HSTORE@ encoder.++Accepts:++* An implementation of the @foldl@ function+(e.g., @Data.Foldable.'foldl''@),+which determines the input value.++Here's how you can use it to produce a specific encoder:++@+hashMapHStore :: Data.HashMap.Strict.HashMap Text (Maybe Text) -> Builder+hashMapHStore =+ hStoreUsingFoldl foldl'+@+-}+{-# INLINABLE hStoreUsingFoldl #-}+hStoreUsingFoldl :: (forall a. (a -> (Text, Maybe Text) -> a) -> a -> b -> a) -> b -> Builder+hStoreUsingFoldl foldl =+ exit . foldl progress enter+ where+ enter =+ (0, mempty)+ progress (!count, !payload) (key, value) =+ (succ count, payload <> hStoreRow key value)+ exit (count, payload) =+ int4_int count <> payload++{-# INLINE hStoreUsingFoldMapAndSize #-}+hStoreUsingFoldMapAndSize :: (forall a. Monoid a => ((Text, Maybe Text) -> a) -> b -> a) -> Int -> b -> Builder+hStoreUsingFoldMapAndSize foldMap size input =+ int4_int size <> foldMap (uncurry hStoreRow) input++{-# INLINE hStoreFromFoldMapAndSize #-}+hStoreFromFoldMapAndSize :: (forall a. Monoid a => (Text -> Maybe Text -> a) -> a) -> Int -> Builder+hStoreFromFoldMapAndSize foldMap size =+ int4_int size <> foldMap hStoreRow++{-# INLINE hStoreRow #-}+hStoreRow :: Text -> Maybe Text -> Builder+hStoreRow key value =+ sized (text_strict key) <> sizedMaybe text_strict value++{-# INLINE hStore_hashMap #-}+hStore_hashMap :: HashMap Text (Maybe Text) -> Builder+hStore_hashMap input =+ int4_int (F.size input) <>+ F.foldlWithKey' (\payload key value -> payload <> hStoreRow key value) mempty input++{-# INLINE hStore_map #-}+hStore_map :: Map Text (Maybe Text) -> Builder+hStore_map input =+ int4_int (Q.size input) <>+ Q.foldlWithKey' (\payload key value -> payload <> hStoreRow key value) mempty input
library/PostgreSQL/Binary/Numeric.hs view
@@ -18,7 +18,7 @@ nanSignCode = 0xC000 {-# INLINE extractComponents #-}-extractComponents :: Integral a => a -> [Int16]+extractComponents :: Integral a => a -> [Word16] extractComponents = (reverse .) . (. abs) . unfoldr $ \case 0 -> Nothing
library/PostgreSQL/Binary/Prelude.hs view
@@ -48,6 +48,14 @@ ------------------------- import Data.Time as Exports +-- unordered-containers+-------------------------+import Data.HashMap.Strict as Exports (HashMap)++-- containers+-------------------------+import Data.Map.Strict as Exports (Map)+ -- placeholders ------------------------- import Development.Placeholders as Exports
postgresql-binary.cabal view
@@ -1,7 +1,7 @@ name: postgresql-binary version:- 0.10+ 0.12.1 synopsis: Encoders and decoders for the PostgreSQL's binary format description:@@ -40,14 +40,12 @@ GHC==7.10.1, GHC==8.0.1 - source-repository head type: git location: git://github.com/nikita-volkov/postgresql-binary.git - library hs-source-dirs: library@@ -58,10 +56,11 @@ default-language: Haskell2010 exposed-modules:+ PostgreSQL.Binary.Decoding+ PostgreSQL.Binary.Encoding PostgreSQL.Binary.Data- PostgreSQL.Binary.Decoder- PostgreSQL.Binary.Encoder other-modules:+ PostgreSQL.Binary.Encoding.Builders PostgreSQL.Binary.Prelude PostgreSQL.Binary.Integral PostgreSQL.Binary.Interval@@ -72,6 +71,8 @@ build-depends: -- parsing: binary-parser >= 0.5 && < 0.6,+ -- building:+ bytestring-strict-builder >= 0.4.2 && < 0.5, -- data: aeson >= 0.9 && < 2, uuid == 1.3.*,@@ -81,16 +82,16 @@ text >= 1 && < 2, vector >= 0.10 && < 0.13, network-ip >= 0.2 && < 1,+ unordered-containers == 0.2.*,+ containers == 0.5.*, -- errors: loch-th == 0.2.*, placeholders == 0.1.*, -- general:- foldl >= 1.1.1 && < 2, transformers >= 0.3 && < 0.6, base-prelude >= 0.1.19 && < 2, base >= 4.6 && < 5 - -- This test-suite must be executed in a single-thread. test-suite tasty type:@@ -135,7 +136,6 @@ -- general: rerebase >= 1.0.2 && < 2 - benchmark encoding type: exitcode-stdio-1.0@@ -158,7 +158,6 @@ criterion >= 1.0 && < 1.2, -- general: rerebase < 2- benchmark decoding type:
tasty/Main.hs view
@@ -8,8 +8,8 @@ import qualified Test.Tasty.SmallCheck as SmallCheck import qualified Test.Tasty.QuickCheck as QuickCheck import qualified Test.QuickCheck as QuickCheck-import qualified PostgreSQL.Binary.Encoder as Encoder-import qualified PostgreSQL.Binary.Decoder as Decoder+import qualified PostgreSQL.Binary.Encoding as A+import qualified PostgreSQL.Binary.Decoding as B import qualified Data.ByteString as ByteString import qualified Main.DB as DB import qualified Main.Gens as Gens@@ -32,7 +32,7 @@ where jsonb = if version >= 90400- then [stdRoundtrip "jsonb" Gens.aeson PTI.jsonb Encoder.jsonb_ast Decoder.jsonb_ast]+ then [primitiveRoundtrip "jsonb" Gens.aeson PTI.jsonb A.jsonb_ast B.jsonb_ast] else [] other = [@@ -40,7 +40,7 @@ sql = "select (1, 'a')" decoder _ =- Decoder.composite ((,) <$> Decoder.compositeNonNullValue Decoder.int <*> Decoder.compositeNonNullValue Decoder.char)+ B.composite ((,) <$> B.valueComposite B.int <*> B.valueComposite B.char) expected = (1 :: Int64, 'a') in select sql decoder expected@@ -49,27 +49,27 @@ sql = "select (1, null)" decoder _ =- Decoder.composite ((,) <$> Decoder.compositeNonNullValue Decoder.int <*> Decoder.compositeValue Decoder.char)+ B.composite ((,) <$> B.valueComposite B.int <*> B.nullableValueComposite B.char) expected = (1 :: Int64, Nothing :: Maybe Char) in select sql decoder expected , select "SELECT '1 year 2 months 3 days 4 hours 5 minutes 6 seconds 332211 microseconds' :: interval"- (bool Decoder.interval_float Decoder.interval_int)+ (bool B.interval_float B.interval_int) (picosecondsToDiffTime (10^6 * (332211 + 10^6 * (6 + 60 * (5 + 60 * (4 + 24 * (3 + 31 * (2 + 12)))))))) , select "SELECT '10 seconds' :: interval"- (bool Decoder.interval_float Decoder.interval_int)+ (bool B.interval_float B.interval_int) (10 :: DiffTime) , HUnit.testCase "Interval encoder: 10 seconds" $ let pti = PTI.interval- encoder =- (bool Encoder.interval_float Encoder.interval_int)+ encoder integerDatetimes =+ (bool A.interval_float A.interval_int integerDatetimes) decoder =- (bool Decoder.interval_float Decoder.interval_int)+ (bool B.interval_float B.interval_int) value = (10 :: DiffTime) in@@ -77,113 +77,114 @@ IO.roundtrip (PTI.oidPQ (PTI.ptiOID pti)) encoder decoder value , timeRoundtrip "interval" Gens.intervalDiffTime PTI.interval- (bool Encoder.interval_float Encoder.interval_int)- (bool Decoder.interval_float Decoder.interval_int)+ (bool A.interval_float A.interval_int)+ (bool B.interval_float B.interval_int) , timeRoundtrip "timestamp" (fmap Apx Gens.auto) PTI.timestamp- ((. unApx) . bool Encoder.timestamp_float Encoder.timestamp_int)- (fmap Apx . bool Decoder.timestamp_float Decoder.timestamp_int)+ ((. unApx) . bool A.timestamp_float A.timestamp_int)+ (fmap Apx . bool B.timestamp_float B.timestamp_int) , HUnit.testCase "timestamptz offset" $ do Right (textual, decoded) <- DB.session $ do integerDatetimes <- DB.integerDatetimes- let encoder = bool Encoder.timestamptz_float Encoder.timestamptz_int integerDatetimes- decoder = bool Decoder.timestamptz_float Decoder.timestamptz_int integerDatetimes+ let encoder = bool A.timestamptz_float A.timestamptz_int integerDatetimes+ decoder = bool B.timestamptz_float B.timestamptz_int integerDatetimes DB.unit "DROP TABLE IF EXISTS a" [] DB.unit "CREATE TABLE a (b TIMESTAMPTZ)" [] DB.unit "set timezone to 'America/Los_Angeles'" [] let p = (,,) (PTI.oidPQ (PTI.ptiOID PTI.timestamptz))- (Encoder.run encoder x) + ((A.encodingBytes . encoder) x) (LibPQ.Binary) x = read "2011-09-28 00:17:25" DB.unit "insert into a (b) values ($1)" [Just p] DB.unit "set timezone to 'Europe/Stockholm'" [] textual <- DB.oneRow "SELECT * FROM a" [] LibPQ.Text- decoded <- fmap (Decoder.run decoder) (DB.oneRow "SELECT * FROM a" [] LibPQ.Binary)+ decoded <- fmap (B.valueParser decoder) (DB.oneRow "SELECT * FROM a" [] LibPQ.Binary) return (textual, decoded) HUnit.assertEqual "" ("2011-09-28 02:17:25+02") textual HUnit.assertEqual "" (Right (read "2011-09-28 00:17:25")) decoded , timeRoundtrip "timestamptz" (fmap Apx Gens.auto) PTI.timestamptz- ((. unApx) . bool Encoder.timestamptz_float Encoder.timestamptz_int)- (fmap Apx . bool Decoder.timestamptz_float Decoder.timestamptz_int)+ ((. unApx) . bool A.timestamptz_float A.timestamptz_int)+ (fmap Apx . bool B.timestamptz_float B.timestamptz_int) , timeRoundtrip "timetz" (fmap Apx Gens.timetz) PTI.timetz- ((. unApx) . bool Encoder.timetz_float Encoder.timetz_int)- (fmap Apx . bool Decoder.timetz_float Decoder.timetz_int)+ ((. unApx) . bool A.timetz_float A.timetz_int)+ (fmap Apx . bool B.timetz_float B.timetz_int) , timeRoundtrip "time" (fmap Apx Gens.auto) PTI.time- ((. unApx) . bool Encoder.time_float Encoder.time_int)- (fmap Apx . bool Decoder.time_float Decoder.time_int)+ ((. unApx) . bool A.time_float A.time_int)+ (fmap Apx . bool B.time_float B.time_int) ,- stdRoundtrip "numeric" Gens.scientific PTI.numeric Encoder.numeric Decoder.numeric+ primitiveRoundtrip "numeric" Gens.scientific PTI.numeric A.numeric B.numeric ,- select "SELECT -1234560.789 :: numeric" (const Decoder.numeric) (read "-1234560.789")+ select "SELECT -1234560.789 :: numeric" (const B.numeric) (read "-1234560.789") ,- select "SELECT -0.0789 :: numeric" (const Decoder.numeric) (read "-0.0789")+ select "SELECT -0.0789 :: numeric" (const B.numeric) (read "-0.0789") ,- select "SELECT 10000 :: numeric" (const Decoder.numeric) (read "10000")+ select "SELECT 10000 :: numeric" (const B.numeric) (read "10000") ,- stdRoundtrip "float4" Gens.auto PTI.float4 Encoder.float4 Decoder.float4+ primitiveRoundtrip "float4" Gens.auto PTI.float4 A.float4 B.float4 ,- stdRoundtrip "float8" Gens.auto PTI.float8 Encoder.float8 Decoder.float8+ primitiveRoundtrip "float8" Gens.auto PTI.float8 A.float8 B.float8 ,- stdRoundtrip "char" Gens.char PTI.text Encoder.char Decoder.char+ primitiveRoundtrip "char" Gens.char PTI.text A.char_utf8 B.char ,- stdRoundtrip "text_strict" Gens.text PTI.text Encoder.text_strict Decoder.text_strict+ primitiveRoundtrip "text_strict" Gens.text PTI.text A.text_strict B.text_strict ,- stdRoundtrip "text_lazy" (fmap convert Gens.text) PTI.text Encoder.text_lazy Decoder.text_lazy+ primitiveRoundtrip "text_lazy" (fmap convert Gens.text) PTI.text A.text_lazy B.text_lazy ,- stdRoundtrip "bytea_strict" Gens.auto PTI.bytea Encoder.bytea_strict Decoder.bytea_strict+ primitiveRoundtrip "bytea_strict" Gens.auto PTI.bytea A.bytea_strict B.bytea_strict ,- stdRoundtrip "bytea_lazy" Gens.auto PTI.bytea Encoder.bytea_lazy Decoder.bytea_lazy+ primitiveRoundtrip "bytea_lazy" Gens.auto PTI.bytea A.bytea_lazy B.bytea_lazy ,- stdRoundtrip "uuid" Gens.uuid PTI.uuid Encoder.uuid Decoder.uuid+ primitiveRoundtrip "uuid" Gens.uuid PTI.uuid A.uuid B.uuid ,- stdRoundtrip "inet" Gens.inet PTI.inet Encoder.inet Decoder.inet+ primitiveRoundtrip "inet" Gens.inet PTI.inet A.inet B.inet ,- stdRoundtrip "int2_int16" Gens.auto PTI.int2 Encoder.int2_int16 Decoder.int+ primitiveRoundtrip "int2_int16" Gens.auto PTI.int2 A.int2_int16 B.int ,- stdRoundtrip "int2_word16" Gens.auto PTI.int2 Encoder.int2_word16 Decoder.int+ primitiveRoundtrip "int2_word16" Gens.auto PTI.int2 A.int2_word16 B.int ,- stdRoundtrip "int4_int32" Gens.auto PTI.int4 Encoder.int4_int32 Decoder.int+ primitiveRoundtrip "int4_int32" Gens.auto PTI.int4 A.int4_int32 B.int ,- stdRoundtrip "int4_word32" Gens.auto PTI.int4 Encoder.int4_word32 Decoder.int+ primitiveRoundtrip "int4_word32" Gens.auto PTI.int4 A.int4_word32 B.int ,- stdRoundtrip "int8_int64" Gens.auto PTI.int8 Encoder.int8_int64 Decoder.int+ primitiveRoundtrip "int8_int64" Gens.auto PTI.int8 A.int8_int64 B.int ,- stdRoundtrip "int8_word64" Gens.auto PTI.int8 Encoder.int8_word64 Decoder.int+ primitiveRoundtrip "int8_word64" Gens.auto PTI.int8 A.int8_word64 B.int ,- stdRoundtrip "bool" Gens.auto PTI.bool Encoder.bool Decoder.bool+ primitiveRoundtrip "bool" Gens.auto PTI.bool A.bool B.bool ,- stdRoundtrip "date" Gens.auto PTI.date Encoder.date Decoder.date+ primitiveRoundtrip "date" Gens.auto PTI.date A.date B.date , let decoder =- Decoder.array $- Decoder.arrayDimension replicateM $- Decoder.arrayDimension replicateM $- Decoder.arrayNonNullValue $- Decoder.int+ B.array $+ B.dimensionArray replicateM $+ B.dimensionArray replicateM $+ B.valueArray $+ B.int in select "SELECT ARRAY[ARRAY[1,2],ARRAY[3,4]]" (const decoder) ([[1,2],[3,4]] :: [[Int]]) , let encoder =- Encoder.array (PTI.oidWord32 (PTI.ptiOID PTI.int8)) $- Encoder.arrayDimension foldl' $- Encoder.arrayDimension foldl' $- Encoder.arrayDimension foldl' $- Encoder.arrayValue $- Encoder.int8_int64+ A.array (PTI.oidWord32 (PTI.ptiOID PTI.int8)) . arrayEncoder+ where+ arrayEncoder =+ A.dimensionArray foldl' $+ A.dimensionArray foldl' $+ A.dimensionArray foldl' $+ A.encodingArray . A.int8_int64 decoder =- Decoder.array $- Decoder.arrayDimension replicateM $- Decoder.arrayDimension replicateM $- Decoder.arrayDimension replicateM $- Decoder.arrayNonNullValue $- Decoder.int+ B.array $+ B.dimensionArray replicateM $+ B.dimensionArray replicateM $+ B.dimensionArray replicateM $+ B.valueArray $+ B.int in arrayCodec (Gens.array3 Gens.auto) encoder decoder ,@@ -191,19 +192,20 @@ pti = PTI.text encoder =- Encoder.array (PTI.oidWord32 (PTI.ptiOID pti)) $- Encoder.arrayDimension foldl' $- Encoder.arrayDimension foldl' $- Encoder.arrayDimension foldl' $- Encoder.arrayValue $- Encoder.text_strict+ A.array (PTI.oidWord32 (PTI.ptiOID pti)) . arrayEncoder+ where+ arrayEncoder =+ A.dimensionArray foldl' $+ A.dimensionArray foldl' $+ A.dimensionArray foldl' $+ A.encodingArray . A.text_strict decoder =- Decoder.array $- Decoder.arrayDimension replicateM $- Decoder.arrayDimension replicateM $- Decoder.arrayDimension replicateM $- Decoder.arrayNonNullValue $- Decoder.text_strict+ B.array $+ B.dimensionArray replicateM $+ B.dimensionArray replicateM $+ B.dimensionArray replicateM $+ B.valueArray $+ B.text_strict in arrayRoundtrip (Gens.array3 Gens.text) pti encoder decoder ]@@ -211,27 +213,27 @@ textual = testGroup "Textual format" $ [- test "numeric" Gens.scientific PTI.numeric TextEncoder.numeric (const Decoder.numeric)+ test "numeric" Gens.scientific PTI.numeric TextEncoder.numeric (const B.numeric) ,- test "float4" Gens.auto PTI.float4 TextEncoder.float4 (const Decoder.float4)+ test "float4" Gens.auto PTI.float4 TextEncoder.float4 (const B.float4) ,- test "float8" Gens.auto PTI.float8 TextEncoder.float8 (const Decoder.float8)+ test "float8" Gens.auto PTI.float8 TextEncoder.float8 (const B.float8) ,- test "uuid" Gens.uuid PTI.uuid TextEncoder.uuid (const Decoder.uuid)+ test "uuid" Gens.uuid PTI.uuid TextEncoder.uuid (const B.uuid) ,- test "int2_int16" Gens.auto PTI.int2 TextEncoder.int2_int16 (const Decoder.int)+ test "int2_int16" Gens.auto PTI.int2 TextEncoder.int2_int16 (const B.int) ,- test "int2_word16" Gens.postgresInt PTI.int2 TextEncoder.int2_word16 (const Decoder.int)+ test "int2_word16" Gens.postgresInt PTI.int2 TextEncoder.int2_word16 (const B.int) ,- test "int4_int32" Gens.auto PTI.int4 TextEncoder.int4_int32 (const Decoder.int)+ test "int4_int32" Gens.auto PTI.int4 TextEncoder.int4_int32 (const B.int) ,- test "int4_word32" Gens.postgresInt PTI.int4 TextEncoder.int4_word32 (const Decoder.int)+ test "int4_word32" Gens.postgresInt PTI.int4 TextEncoder.int4_word32 (const B.int) ,- test "int8_int64" Gens.auto PTI.int8 TextEncoder.int8_int64 (const Decoder.int)+ test "int8_int64" Gens.auto PTI.int8 TextEncoder.int8_int64 (const B.int) ,- test "int8_word64" Gens.postgresInt PTI.int8 TextEncoder.int8_word64 (const Decoder.int)+ test "int8_word64" Gens.postgresInt PTI.int8 TextEncoder.int8_word64 (const B.int) ,- test "bool" Gens.auto PTI.bool TextEncoder.bool (const Decoder.bool)+ test "bool" Gens.auto PTI.bool TextEncoder.bool (const B.bool) ] where test typeName gen pti encoder decoder =@@ -241,7 +243,7 @@ arrayCodec gen encoder decoder = QuickCheck.testProperty ("Array codec") $ QuickCheck.forAll gen $- \value -> (QuickCheck.===) (Right value) (Decoder.run decoder (Encoder.run encoder value))+ \value -> (QuickCheck.===) (Right value) (B.valueParser decoder ((A.encodingBytes . encoder) value)) arrayRoundtrip gen pti encoder decoder = QuickCheck.testProperty ("Array roundtrip") $@@ -251,9 +253,12 @@ QuickCheck.testProperty (typeName <> " roundtrip") $ QuickCheck.forAll gen $ Properties.stdRoundtrip (PTI.oidPQ (PTI.ptiOID pti)) encoder decoder +primitiveRoundtrip typeName gen pti encoder decoder =+ stdRoundtrip typeName gen pti (encoder) decoder+ timeRoundtrip typeName gen pti encoder decoder = QuickCheck.testProperty (typeName <> " roundtrip") $- QuickCheck.forAll gen $ Properties.roundtrip (PTI.oidPQ (PTI.ptiOID pti)) encoder decoder+ QuickCheck.forAll gen $ Properties.roundtrip (PTI.oidPQ (PTI.ptiOID pti)) (\x -> encoder x) decoder select statement decoder value = HUnit.testCase (show statement) $
tasty/Main/Gens.hs view
@@ -9,8 +9,7 @@ import qualified Data.UUID as UUID import qualified Data.Vector as Vector import qualified Data.HashMap.Strict as HashMap-import qualified PostgreSQL.Binary.Data as Data-import qualified PostgreSQL.Binary.Encoder as Encoder+import qualified PostgreSQL.Binary.Encoding as Encoder import qualified Data.Text as Text import qualified Data.Aeson as Aeson import qualified Network.IP.Addr as IPAddr@@ -128,34 +127,6 @@ then IPAddr.netAddr <$> (IPAddr.IPv6 <$> (IPAddr.ip6FromWords <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary)) <*> choose (0, 128) else IPAddr.netAddr <$> (IPAddr.IPv4 <$> (IPAddr.ip4FromOctets <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary)) <*> choose (0, 32)--arrayRep :: Gen (Word32, Data.Array)-arrayRep =- do- ndims <- choose (1, 4)- dims <- Vector.replicateM ndims dimGen- (valueGen', oid, arrayOID) <- valueGen- values <- Vector.replicateM (dimsToNValues dims) valueGen'- let nulls = Vector.elem Nothing values- return (arrayOID, (dims, values, nulls, oid))- where- dimGen =- (,) <$> choose (1, 7) <*> pure 1- valueGen =- do- (pti, gen) <- elements [(PTI.int8, mkGen Encoder.int8_int64),- (PTI.bool, mkGen Encoder.bool),- (PTI.date, mkGen Encoder.date),- (PTI.text, mkGen Encoder.text_strict),- (PTI.bytea, mkGen Encoder.bytea_strict)]- return (gen, PTI.oidWord32 (PTI.ptiOID pti), PTI.oidWord32 (fromJust (PTI.ptiArrayOID pti)))- where- mkGen renderer =- fmap (fmap (convert . renderer)) arbitrary- dimsToNValues =- Vector.product . fmap dimensionWidth- where- dimensionWidth (x, _) = fromIntegral x array3 :: Gen a -> Gen [[[a]]] array3 gen =
tasty/Main/IO.hs view
@@ -12,19 +12,18 @@ import qualified Data.UUID as UUID import qualified Data.Vector as Vector import qualified Data.Text.Encoding as Text-import qualified PostgreSQL.Binary.Data as Data-import qualified PostgreSQL.Binary.Encoder as Encoder-import qualified PostgreSQL.Binary.Decoder as Decoder+import qualified PostgreSQL.Binary.Decoding as A+import qualified PostgreSQL.Binary.Encoding as B import qualified Database.PostgreSQL.LibPQ as LibPQ -textRoundtrip :: LibPQ.Oid -> TextEncoder.Encoder a -> (Bool -> Decoder.Decoder a) -> a -> IO (Either Text a)+textRoundtrip :: LibPQ.Oid -> TextEncoder.Encoder a -> (Bool -> A.Value a) -> a -> IO (Either Text a) textRoundtrip oid encoder decoder value = fmap (either (Left . Text.decodeUtf8) id) $ DB.session $ do integerDatetimes <- DB.integerDatetimes bytes <- DB.oneRow "SELECT $1" (params integerDatetimes) LibPQ.Binary- return $ Decoder.run (decoder integerDatetimes) bytes+ return $ A.valueParser (decoder integerDatetimes) bytes where params integerDatetimes = [ Just ( oid , bytes , LibPQ.Text ) ]@@ -32,24 +31,24 @@ bytes = (convert . encoder) value -roundtrip :: LibPQ.Oid -> (Bool -> Encoder.Encoder a) -> (Bool -> Decoder.Decoder b) -> a -> IO (Either Text b)+roundtrip :: LibPQ.Oid -> (Bool -> a -> B.Encoding) -> (Bool -> A.Value b) -> a -> IO (Either Text b) roundtrip oid encoder decoder value = fmap (either (Left . Text.decodeUtf8) id) $ DB.session $ do integerDatetimes <- DB.integerDatetimes bytes <- DB.oneRow "SELECT $1" (params integerDatetimes) LibPQ.Binary- return $ Decoder.run (decoder integerDatetimes) bytes+ return $ A.valueParser (decoder integerDatetimes) bytes where params integerDatetimes = [ Just ( oid , bytes , LibPQ.Binary ) ] where bytes =- (convert . encoder integerDatetimes) value+ (B.encodingBytes . encoder integerDatetimes) value -parameterlessStatement :: ByteString -> (Bool -> Decoder.Decoder a) -> a -> IO (Either Text a)+parameterlessStatement :: ByteString -> (Bool -> A.Value a) -> a -> IO (Either Text a) parameterlessStatement statement decoder value = fmap (either (Left . Text.decodeUtf8) id) $ DB.session $ do integerDatetimes <- DB.integerDatetimes bytes <- DB.oneRow statement [] LibPQ.Binary- return $ Decoder.run (decoder integerDatetimes) bytes+ return $ A.valueParser (decoder integerDatetimes) bytes
tasty/Main/Properties.hs view
@@ -6,10 +6,9 @@ import qualified Data.Scientific as Scientific import qualified Data.UUID as UUID import qualified Data.Vector as Vector-import qualified PostgreSQL.Binary.Data as Data-import qualified PostgreSQL.Binary.Encoder as Encoder-import qualified PostgreSQL.Binary.Decoder as Decoder-import qualified Main.TextEncoder as TextEncoder +import qualified PostgreSQL.Binary.Decoding as A+import qualified PostgreSQL.Binary.Encoding as B+import qualified Main.TextEncoder as C import qualified Main.PTI as PTI import qualified Main.DB as DB import qualified Main.IO as IO@@ -17,14 +16,14 @@ roundtrip :: (Show a, Eq a) => - LibPQ.Oid -> (Bool -> Encoder.Encoder a) -> (Bool -> Decoder.Decoder a) -> a -> Property+ LibPQ.Oid -> (Bool -> (a -> B.Encoding)) -> (Bool -> A.Value a) -> a -> Property roundtrip oid encoder decoder value = Right value === unsafePerformIO (IO.roundtrip oid encoder decoder value) -stdRoundtrip :: (Show a, Eq a) => LibPQ.Oid -> Encoder.Encoder a -> Decoder.Decoder a -> a -> Property+stdRoundtrip :: (Show a, Eq a) => LibPQ.Oid -> (a -> B.Encoding) -> A.Value a -> a -> Property stdRoundtrip oid encoder decoder value = roundtrip oid (const encoder) (const decoder) value -textRoundtrip :: (Show a, Eq a) => LibPQ.Oid -> TextEncoder.Encoder a -> (Bool -> Decoder.Decoder a) -> a -> Property+textRoundtrip :: (Show a, Eq a) => LibPQ.Oid -> C.Encoder a -> (Bool -> A.Value a) -> a -> Property textRoundtrip oid encoder decoder value = Right value === unsafePerformIO (IO.textRoundtrip oid encoder decoder value)