Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d618b76
Faster Generically derived row decoders
mzabani Aug 12, 2026
668a3ed
Add decoding offset to hopefully reduce ByteString allocations
mzabani Aug 11, 2026
75c6d2c
Multiple-rows-at-once decoding
mzabani Aug 11, 2026
eeef5df
Get rid of all ByteStrings during parsing
mzabani Aug 11, 2026
6ead147
Special n>0 optimization makes no sense anymore
mzabani Aug 11, 2026
862c1f0
Add comment on strictness
mzabani Aug 12, 2026
d39eaa9
Slightly better (but not any faster) specialized row decoder
mzabani Aug 12, 2026
e9d01e1
Add a test for the FromPgMessage DataRow decoder
mzabani Aug 12, 2026
682125c
Try a new kind of very specialized parser for small types
mzabani Aug 8, 2026
f6a80a5
Post-rebase fixing
mzabani Aug 12, 2026
5c16f6b
More specialized instances, more confirmation of benefits
mzabani Aug 12, 2026
91aad56
Specialized instance for UTCTime => clear benefits once again
mzabani Aug 12, 2026
4b6e260
Tidy up a bit
mzabani Aug 12, 2026
54ba650
Slightly better understanding of inlining, clearer inlining boundaries
mzabani Aug 13, 2026
235d9e1
Add `inlinedSingleFieldDecoder`
mzabani Aug 13, 2026
94a9ece
More comprehensive coverage of types in benchmarks, more specialized …
mzabani Aug 14, 2026
2eea650
Tests for non-specialized field decoders
mzabani Aug 14, 2026
e07e93e
Tidy up GHC Core
mzabani Aug 14, 2026
3a44fb6
Overlapping Maybe instances do help with the inlined row decoder's pe…
mzabani Aug 15, 2026
84b7265
Very experimental change with `Maybe a` instances
mzabani Aug 16, 2026
e5fc6e6
A separate field in FieldDecoder for what to decode NULL to
mzabani Aug 17, 2026
8a0684d
Try to improve the code, but inlining got worse
mzabani Aug 18, 2026
52d7670
Some memory usage improvements
mzabani Aug 19, 2026
4d60b05
Float decodesSqlNullTo outside and add strictness for better inlining
mzabani Aug 19, 2026
eb4372d
Tidy up, a few more INLINE pragmas
mzabani Aug 19, 2026
c5ee605
TODOs in the code
mzabani Aug 19, 2026
cf93810
Trying a specialized notConst method
mzabani Aug 20, 2026
7eddecb
Do the JSON types, but hpgsql-simple-compat will break
mzabani Aug 20, 2026
63c1e97
Create new strict and lazy ByteString-like PinnedByteArray types
mzabani Aug 14, 2026
3e8a092
Stop inlining aggressively in Generically derived row decoders, add a…
mzabani Aug 23, 2026
784d5cf
Don't use inlined row decoders for Tuples
mzabani Aug 23, 2026
b4f37ab
Update TODO
mzabani Aug 23, 2026
19acd1d
Stop exposing performance-oriented FromPgField methods
mzabani Aug 23, 2026
e6efc73
Document why PinnedByteArray
mzabani Aug 23, 2026
c9316c1
Custom conversion from PinnedByteArray to Text
mzabani Aug 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Runfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ tests:
if [ -n "$NIX" ]; then
nix-build --no-out-link -A "testsPg${pg}" --argstr hspecArgs "$TARGS"
else
cabal build hpgsql-tests hpgsql-simple-compat-tests
cabal build hpgsql-tests # hpgsql-simple-compat-tests
nix-shell -A "shellPg${pg}" ./default.nix --run "./scripts/run-tests-db-internal.sh $TARGS"
fi
done
Expand Down Expand Up @@ -104,3 +104,8 @@ tests-compat:
cabal build hpgsql-simple-compat-tests
nix-shell -A "shellPg${pg}" ./default.nix --run "./scripts/run-hpgsql-simple-compat-tests-db-internal.sh $TARGS"
done

ghc-core:
set -eo pipefail
cabal build hpgsql-tests 1>&2
cat dist-newstyle/build/x86_64-linux/ghc-9.10.3/hpgsql-tests-0.1.0.0/x/hpgsql-tests/build/hpgsql-tests/hpgsql-tests-tmp/RowDecoderGhcCore.thr.dump-simpl
9 changes: 9 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- Test both `singleField fieldDecoder` and `singleFieldRowDecoder` for every type in our tests.
- Do _not_ expose new FromPgField methods. Add new EncodingInternal module, instead.
- Check that the non-exposed methods are safe wrt bytearray bounds access by construction, and users can't break that. If that's true, we can omit bounds checks in our row decoding, making row decoders smaller and maybe faster.
- Some types might still not derive specialized row decoders
- "Oh no! No colInfo here.. what do we do!?" in hpgsql-simple-compat. This might require a big rethinking of things..
- Double-check which row encoders we want to use the inlined versions for and which we don't. Tuples?
- Text internals might be easier to use now?
- Expose in the FromPgField class two new methods.. inlined and non inlined row decoders with/without bounds checks. Use with-bounds-checks for MonadicRowDecoder, and without-bounds-checks for regular row decoder, because the latter checks type oids
- Is `notInlinedSingleFieldRowDecoder` worth keeping? The Generically derived decoder is almost as fast. Maybe for types that aren't records it's a different story, though?
1 change: 1 addition & 0 deletions hpgsql-benchmarks/hpgsql-benchmarks.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ executable hpgsql-benchmarks
, hspec-expectations
, postgresql-simple
, resourcet
, scientific
, statistics
, stm
, streaming
Expand Down
77 changes: 58 additions & 19 deletions hpgsql-benchmarks/src/Main.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# OPTIONS_GHC -ddump-simpl -dno-typeable-binds -dsuppress-coercions -dsuppress-module-prefixes -dsuppress-type-applications -ddump-to-file #-}

module Main where

import Control.Concurrent.Async (mapConcurrently)
Expand All @@ -22,6 +24,7 @@ import Criterion.Measurement.Types
)
import qualified Data.ByteString.Char8 as BS8
import Data.Int (Int32, Int64)
import Data.Scientific (Scientific)
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text as Text
Expand All @@ -44,6 +47,7 @@ import Hpgsql.Connection (renderLibpqConnectionString)
import qualified Hpgsql.Connection
import qualified Hpgsql.Connection as Hpgsql
import qualified Hpgsql.Copy
import Hpgsql.Encoding (inlinedSingleFieldRowDecoder, notInlinedSingleFieldRowDecoder)
import qualified Hpgsql.Encoding as Hpgsql
import qualified Hpgsql.Query as Hpgsql
import qualified Hpgsql.Types as Hpgsql
Expand Down Expand Up @@ -79,11 +83,23 @@ data BenchRow = BenchRow
brMaybeInt :: !(Maybe Int),
brMaybeText :: !(Maybe Text),
brMaybeDouble :: !(Maybe Double),
brMaybeDay :: !(Maybe Day)
brMaybeDay :: !(Maybe Day),
brNumeric :: !Scientific,
brFloat :: !Float,
brBool1 :: !Bool,
brBool2 :: !Bool
}
deriving stock (Generic, Show, Eq)
deriving anyclass (NFData, Hpgsql.FromPgRow, PGSimple.FromRow)

notInlinedHandWrittenBenchRowDecoder :: Hpgsql.RowDecoder BenchRow
notInlinedHandWrittenBenchRowDecoder =
BenchRow <$> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder <*> notInlinedSingleFieldRowDecoder

fullyInlinedBenchRowDecoder :: Hpgsql.RowDecoder BenchRow
fullyInlinedBenchRowDecoder =
BenchRow <$> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder <*> inlinedSingleFieldRowDecoder

data HasqlBenchRow = HasqlBenchRow
{ hbrId :: !Int32,
hbrDate1 :: !Day,
Expand All @@ -97,7 +113,11 @@ data HasqlBenchRow = HasqlBenchRow
hbrMaybeInt :: !(Maybe Int32),
hbrMaybeText :: !(Maybe Text),
hbrMaybeDouble :: !(Maybe Double),
hbrMaybeDay :: !(Maybe Day)
hbrMaybeDay :: !(Maybe Day),
hbrNumeric :: !Scientific,
hbrFloat :: !Float,
hbrBool1 :: Bool,
hbrBool2 :: Bool
}
deriving stock (Generic, Show, Eq)
deriving anyclass (NFData)
Expand Down Expand Up @@ -163,12 +183,14 @@ main = do

statsBefore <- getRTSStats
hspecWith defaultConfig {configFormat = Just (formatterToFormat silent)} $ do
let sql17 = "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date, g::numeric, g::float4, g%2=0, g%2=1 FROM generate_series(1,$1) g"
sql17Simple = "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date, g::numeric, g::float4, g%2=0, g%2=1 FROM generate_series(1,?) g"
sql13 = "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,$1) g"
sql13Simple = "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,?) g"
describe "Parsing 13-column rows into a List" $ do
let sql = "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,$1) g"
pgSimpleSql = "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,?) g"
hasqlListStmt =
let hasqlListStmt =
HasqlStmt.Statement
sql
sql13
(HasqlEnc.param (HasqlEnc.nonNullable HasqlEnc.int4))
( HasqlDec.rowList
( (,,,,,,,,,,,,)
Expand All @@ -190,7 +212,7 @@ main = do
True
hasqlRecordListStmt =
HasqlStmt.Statement
sql
sql17
(HasqlEnc.param (HasqlEnc.nonNullable HasqlEnc.int4))
( HasqlDec.rowList
( HasqlBenchRow
Expand All @@ -207,15 +229,19 @@ main = do
<*> HasqlDec.column (HasqlDec.nullable HasqlDec.text)
<*> HasqlDec.column (HasqlDec.nullable HasqlDec.float8)
<*> HasqlDec.column (HasqlDec.nullable HasqlDec.date)
<*> HasqlDec.column (HasqlDec.nonNullable HasqlDec.numeric)
<*> HasqlDec.column (HasqlDec.nonNullable HasqlDec.float4)
<*> HasqlDec.column (HasqlDec.nonNullable HasqlDec.bool)
<*> HasqlDec.column (HasqlDec.nonNullable HasqlDec.bool)
)
)
True
forM_ [10_000 :: Int, 100_000] $ \n -> do
forM_ [100_000 :: Int] $ \n -> do
it ("hpgsql Tuple List (" ++ show n ++ " rows)") $
void $
bench ("hpgsql Tuple List (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections hpgsqlConnect Hpgsql.Connection.closeGracefully $ \conn -> do
Hpgsql.queryWith (Hpgsql.rowDecoder @(Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) conn (Hpgsql.mkQuery sql (Hpgsql.Only n))
Hpgsql.queryWith (Hpgsql.rowDecoder @(Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) conn (Hpgsql.mkQuery sql13 (Hpgsql.Only n))
it ("hasql Tuple List (" ++ show n ++ " rows)") $
void $
bench ("hasql Tuple List (" ++ show n ++ " rows)") $
Expand All @@ -226,12 +252,12 @@ main = do
void $
bench ("postgresql-simple Tuple List (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections pgSimpleConnect PGSimple.close $ \pgSimpleConn -> do
PGSimple.query @_ @(Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day) pgSimpleConn pgSimpleSql (PGSimple.Only n)
PGSimple.query @_ @(Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day) pgSimpleConn sql13Simple (PGSimple.Only n)
it ("hpgsql Record List (" ++ show n ++ " rows)") $
void $
bench ("hpgsql Record List (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections hpgsqlConnect Hpgsql.Connection.closeGracefully $ \conn -> do
Hpgsql.queryWith (Hpgsql.rowDecoder @BenchRow) conn (Hpgsql.mkQuery sql (Hpgsql.Only n))
Hpgsql.queryWith (Hpgsql.rowDecoder @BenchRow) conn (Hpgsql.mkQuery sql17 (Hpgsql.Only n))
it ("hasql Record List (" ++ show n ++ " rows)") $
void $
bench ("hasql Record List (" ++ show n ++ " rows)") $
Expand All @@ -242,40 +268,53 @@ main = do
void $
bench ("postgresql-simple Record List (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections pgSimpleConnect PGSimple.close $ \pgSimpleConn -> do
PGSimple.query @_ @BenchRow pgSimpleConn pgSimpleSql (PGSimple.Only n)
PGSimple.query @_ @BenchRow pgSimpleConn sql17Simple (PGSimple.Only n)
describe "Parsing 13-column rows in streaming fashion" $ do
let sql = "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,$1) g"
forM_ [10_000 :: Int, 100_000] $ \n -> do
forM_ [100_000 :: Int] $ \n -> do
it ("hpgsql Tuple Stream (" ++ show n ++ " rows)") $
void $
bench ("hpgsql Tuple Stream (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections hpgsqlConnect Hpgsql.Connection.closeGracefully $ \conn -> do
res <- Hpgsql.querySWith (Hpgsql.rowDecoder @(Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) conn (Hpgsql.mkQuery sql (Hpgsql.Only n))
res <- Hpgsql.querySWith (Hpgsql.rowDecoder @(Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) conn (Hpgsql.mkQuery sql13 (Hpgsql.Only n))
S.effects res
it ("streaming-postgresql-simple Tuple Stream (" ++ show n ++ " rows)") $
void $
bench ("streaming-postgresql-simple Tuple Stream (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections pgSimpleConnect PGSimple.close $ \pgSimpleConn -> do
runResourceT @IO $ do
let res :: Stream (Of (Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) (ResourceT IO) () = StreamingPostgresSimple.query pgSimpleConn "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,?) g" (PGSimple.Only n)
let res :: Stream (Of (Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) (ResourceT IO) () = StreamingPostgresSimple.query pgSimpleConn sql13Simple (PGSimple.Only n)
S.effects res
it ("postgresql-simple Tuple fold (" ++ show n ++ " rows)") $
void $
bench ("postgresql-simple Tuple fold (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections pgSimpleConnect PGSimple.close $ \pgSimpleConn -> do
PGSimple.fold pgSimpleConn "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,?) g" (PGSimple.Only n) () (\() (!_ :: (Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) -> pure ())
PGSimple.fold pgSimpleConn sql13Simple (PGSimple.Only n) () (\() (!_ :: (Int, Day, Day, UTCTime, UTCTime, Text, Text, Double, Double, Maybe Int, Maybe Text, Maybe Double, Maybe Day)) -> pure ())
describe "Parsing 17-column rows in streaming fashion" $ do
forM_ [100_000 :: Int] $ \n -> do
it ("hpgsql Record Stream (" ++ show n ++ " rows)") $
void $
bench ("hpgsql Record Stream (" ++ show n ++ " rows)") $ do
withMultipleConnections numConcurrentConnections hpgsqlConnect Hpgsql.Connection.closeGracefully $ \conn -> do
res <- Hpgsql.querySWith (Hpgsql.rowDecoder @BenchRow) conn (Hpgsql.mkQuery sql (Hpgsql.Only n))
res <- Hpgsql.querySWith (Hpgsql.rowDecoder @BenchRow) conn (Hpgsql.mkQuery sql17 (Hpgsql.Only n))
S.effects res
it ("hpgsql Record Stream (" ++ show n ++ " rows, hand-written row decoder)") $
void $
bench ("hpgsql Record Stream (" ++ show n ++ " rows, hand-written row decoder)") $ do
withMultipleConnections numConcurrentConnections hpgsqlConnect Hpgsql.Connection.closeGracefully $ \conn -> do
res <- Hpgsql.querySWith notInlinedHandWrittenBenchRowDecoder conn (Hpgsql.mkQuery sql17 (Hpgsql.Only n))
S.effects res
it ("hpgsql Record Stream (" ++ show n ++ " rows, fully inlined row decoder)") $
void $
bench ("hpgsql Record Stream (" ++ show n ++ " rows, fully inlined row decoder)") $ do
withMultipleConnections numConcurrentConnections hpgsqlConnect Hpgsql.Connection.closeGracefully $ \conn -> do
res <- Hpgsql.querySWith fullyInlinedBenchRowDecoder conn (Hpgsql.mkQuery sql17 (Hpgsql.Only n))
S.effects res
it ("streaming-postgresql-simple Record Stream (" ++ show n ++ " rows)") $
void $
bench ("streaming-postgresql-simple Record Stream (" ++ show n ++ " rows)") $
withMultipleConnections numConcurrentConnections pgSimpleConnect PGSimple.close $ \pgSimpleConn -> do
runResourceT @IO $ do
let res :: Stream (Of BenchRow) (ResourceT IO) () = StreamingPostgresSimple.query pgSimpleConn "SELECT g, ('2000-01-01'::date + g::int4), ('2000-06-15'::date + g::int4), ('2000-01-01T00:00:00Z'::timestamptz + g * interval '1 second'), ('2020-06-15T12:00:00Z'::timestamptz + g * interval '1 minute'), 'row-' || g::text, 'item-' || g::text, g::float8 * 1.5, g::float8 * 2.5, NULL::int4, NULL::text, NULL::float8, NULL::date FROM generate_series(1,?) g" (PGSimple.Only n)
let res :: Stream (Of BenchRow) (ResourceT IO) () = StreamingPostgresSimple.query pgSimpleConn sql17Simple (PGSimple.Only n)
S.effects res
it ("postgresql-simple Record fold (" ++ show n ++ " rows)") $
void $
Expand Down
10 changes: 7 additions & 3 deletions hpgsql-simple-compat/src/Database/PostgreSQL/Simple/FromField.hs
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,13 @@ class FromField a where
let dec = Hpgsql.fieldDecoder
in \f ->
if Hpgsql.allowedPgTypes dec f
then \mbs -> Conversion $ \_encCtx -> case Hpgsql.fieldValueDecoder dec f mbs of
Right v -> Ok v
Left err -> Errors [toException $ userError err]
then \mbs -> Conversion $ \_encCtx -> case mbs of
Nothing -> case dec.decodesSqlNullTo of
Left err -> Errors [toException $ userError err]
Right v -> Ok v
Just bs -> case Hpgsql.fieldValueDecoder dec f bs of
Right v -> Ok v
Left err -> Errors [toException $ userError err]
else \_ -> Conversion $ \_encCtx -> Errors [toException $ userError "Invalid type OID for FromField instance"]

instance FromField ()
Expand Down
Loading