From 361e088e1c37eb4407253698ec21a6c103f1c856 Mon Sep 17 00:00:00 2001 From: Robert Vollmert Date: Wed, 29 Jun 2022 12:19:05 +0200 Subject: [PATCH 1/5] exec, query: factor out common parameter handling helpers There were two identical copies each of the parameter mangling code. --- src/Database/PostgreSQL/LibPQ.hsc | 167 ++++++++++++++---------------- 1 file changed, 78 insertions(+), 89 deletions(-) diff --git a/src/Database/PostgreSQL/LibPQ.hsc b/src/Database/PostgreSQL/LibPQ.hsc index 8496879..3f8a00d 100644 --- a/src/Database/PostgreSQL/LibPQ.hsc +++ b/src/Database/PostgreSQL/LibPQ.hsc @@ -746,6 +746,61 @@ newtype Oid = Oid CUInt deriving (Eq, Ord, Read, Show, Storable, Typeable) invalidOid :: Oid invalidOid = Oid (#const InvalidOid) + +-- | Convert a list of parameters to the format expected by libpq FFI calls. +withParams :: [Maybe (Oid, B.ByteString, Format)] + -> (CInt -> Ptr Oid -> Ptr CString -> Ptr CInt -> Ptr CInt -> IO a) + -> IO a +withParams params action = + withArray oids $ \ts -> + withMany (maybeWith B.useAsCString) values $ \c_values -> + withArray c_values $ \vs -> + withArray c_lengths $ \ls -> + withArray formats $ \fs -> + action n ts vs ls fs + where + (oids, values, lengths, formats) = + foldl' accum ([],[],[],[]) $ reverse params + !c_lengths = map toEnum lengths :: [CInt] + !n = toEnum $ length params + + accum (!a,!b,!c,!d) Nothing = ( invalidOid:a + , Nothing:b + , 0:c + , 0:d + ) + accum (!a,!b,!c,!d) (Just (t,v,f)) = ( t:a + , (Just v):b + , (B.length v):c + , (toEnum $ fromEnum f):d + ) + +-- | Convert a list of parameters to the format expected by libpq FFI calls, +-- prepared statement variant. +withParamsPrepared :: [Maybe (B.ByteString, Format)] + -> (CInt -> Ptr CString -> Ptr CInt -> Ptr CInt -> IO a) + -> IO a +withParamsPrepared params action = + withMany (maybeWith B.useAsCString) values $ \c_values -> + withArray c_values $ \vs -> + withArray c_lengths $ \ls -> + withArray formats $ \fs -> + action n vs ls fs + where + (values, lengths, formats) = foldl' accum ([],[],[]) $ reverse params + !c_lengths = map toEnum lengths :: [CInt] + !n = toEnum $ length params + + accum (!a,!b,!c) Nothing = ( Nothing:a + , 0:b + , 0:c + ) + accum (!a,!b,!c) (Just (v, f)) = ( (Just v):a + , (B.length v):b + , (toEnum $ fromEnum f):c + ) + + -- | Submits a command to the server and waits for the result. -- -- Returns a 'Result' or possibly 'Nothing'. A 'Result' will generally @@ -812,31 +867,12 @@ execParams :: Connection -- ^ connection -> Format -- ^ result format -> IO (Maybe Result) -- ^ result execParams connection statement params rFmt = - do let (oids, values, lengths, formats) = - foldl' accum ([],[],[],[]) $ reverse params - !c_lengths = map toEnum lengths :: [CInt] - !n = toEnum $ length params - !f = toEnum $ fromEnum rFmt - resultFromConn connection $ \c -> - B.useAsCString statement $ \s -> - withArray oids $ \ts -> - withMany (maybeWith B.useAsCString) values $ \c_values -> - withArray c_values $ \vs -> - withArray c_lengths $ \ls -> - withArray formats $ \fs -> - c_PQexecParams c s n ts vs ls fs f - - where - accum (!a,!b,!c,!d) Nothing = ( invalidOid:a - , Nothing:b - , 0:c - , 0:d - ) - accum (!a,!b,!c,!d) (Just (t,v,f)) = ( t:a - , (Just v):b - , (B.length v):c - , (toEnum $ fromEnum f):d - ) + resultFromConn connection $ \c -> + B.useAsCString statement $ \s -> + withParams params $ \n ts vs ls fs -> + c_PQexecParams c s n ts vs ls fs f + where + !f = toEnum $ fromEnum rFmt -- | Submits a request to create a prepared statement with the given @@ -911,28 +947,13 @@ execPrepared :: Connection -- ^ connection -> [Maybe (B.ByteString, Format)] -- ^ parameters -> Format -- ^ result format -> IO (Maybe Result) -- ^ result -execPrepared connection stmtName mPairs rFmt = - do let (values, lengths, formats) = foldl' accum ([],[],[]) $ reverse mPairs - !c_lengths = map toEnum lengths :: [CInt] - !n = toEnum $ length mPairs - !f = toEnum $ fromEnum rFmt - resultFromConn connection $ \c -> - B.useAsCString stmtName $ \s -> - withMany (maybeWith B.useAsCString) values $ \c_values -> - withArray c_values $ \vs -> - withArray c_lengths $ \ls -> - withArray formats $ \fs -> - c_PQexecPrepared c s n vs ls fs f - +execPrepared connection stmtName params rFmt = + resultFromConn connection $ \c -> + B.useAsCString stmtName $ \s -> + withParamsPrepared params $ \n vs ls fs -> + c_PQexecPrepared c s n vs ls fs f where - accum (!a,!b,!c) Nothing = ( Nothing:a - , 0:b - , 0:c - ) - accum (!a,!b,!c) (Just (v, f)) = ( (Just v):a - , (B.length v):b - , (toEnum $ fromEnum f):c - ) + !f = toEnum $ fromEnum rFmt -- | Submits a request to obtain information about the specified @@ -1676,31 +1697,13 @@ sendQueryParams :: Connection -> Format -> IO Bool sendQueryParams connection statement params rFmt = - do let (oids, values, lengths, formats) = - foldl' accum ([],[],[],[]) $ reverse params - !c_lengths = map toEnum lengths :: [CInt] - !n = toEnum $ length params - !f = toEnum $ fromEnum rFmt - enumFromConn connection $ \c -> - B.useAsCString statement $ \s -> - withArray oids $ \ts -> - withMany (maybeWith B.useAsCString) values $ \c_values -> - withArray c_values $ \vs -> - withArray c_lengths $ \ls -> - withArray formats $ \fs -> - c_PQsendQueryParams c s n ts vs ls fs f + enumFromConn connection $ \c -> + B.useAsCString statement $ \s -> + withParams params $ \n ts vs ls fs -> + c_PQsendQueryParams c s n ts vs ls fs f where - accum (!a,!b,!c,!d) Nothing = ( invalidOid:a - , Nothing:b - , 0:c - , 0:d - ) - accum (!a,!b,!c,!d) (Just (t,v,f)) = ( t:a - , (Just v):b - , (B.length v):c - , (toEnum $ fromEnum f):d - ) + !f = toEnum $ fromEnum rFmt -- | Sends a request to create a prepared statement with the given @@ -1726,28 +1729,14 @@ sendQueryPrepared :: Connection -> [Maybe (B.ByteString, Format)] -> Format -> IO Bool -sendQueryPrepared connection stmtName mPairs rFmt = - do let (values, lengths, formats) = foldl' accum ([],[],[]) $ reverse mPairs - !c_lengths = map toEnum lengths :: [CInt] - !n = toEnum $ length mPairs - !f = toEnum $ fromEnum rFmt - enumFromConn connection $ \c -> - B.useAsCString stmtName $ \s -> - withMany (maybeWith B.useAsCString) values $ \c_values -> - withArray c_values $ \vs -> - withArray c_lengths $ \ls -> - withArray formats $ \fs -> - c_PQsendQueryPrepared c s n vs ls fs f +sendQueryPrepared connection stmtName params rFmt = + enumFromConn connection $ \c -> + B.useAsCString stmtName $ \s -> + withParamsPrepared params $ \n vs ls fs -> + c_PQsendQueryPrepared c s n vs ls fs f where - accum (!a,!b,!c) Nothing = ( Nothing:a - , 0:b - , 0:c - ) - accum (!a,!b,!c) (Just (v, f)) = ( (Just v):a - , (B.length v):b - , (toEnum $ fromEnum f):c - ) + !f = toEnum $ fromEnum rFmt -- | Submits a request to obtain information about the specified From bc69f4bbcef4bbcf806d5bcf567e5fb20ebd3323 Mon Sep 17 00:00:00 2001 From: Robert Vollmert Date: Wed, 29 Jun 2022 12:33:16 +0200 Subject: [PATCH 2/5] withParams*: drop redundant map Instead of building a list of 'Int' in the fold, and then mapping 'toEnum' over it, build a list of 'CInt' directly. --- src/Database/PostgreSQL/LibPQ.hsc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Database/PostgreSQL/LibPQ.hsc b/src/Database/PostgreSQL/LibPQ.hsc index 3f8a00d..dc08473 100644 --- a/src/Database/PostgreSQL/LibPQ.hsc +++ b/src/Database/PostgreSQL/LibPQ.hsc @@ -759,9 +759,8 @@ withParams params action = withArray formats $ \fs -> action n ts vs ls fs where - (oids, values, lengths, formats) = + (oids, values, c_lengths, formats) = foldl' accum ([],[],[],[]) $ reverse params - !c_lengths = map toEnum lengths :: [CInt] !n = toEnum $ length params accum (!a,!b,!c,!d) Nothing = ( invalidOid:a @@ -771,7 +770,7 @@ withParams params action = ) accum (!a,!b,!c,!d) (Just (t,v,f)) = ( t:a , (Just v):b - , (B.length v):c + , (toEnum $ B.length v):c , (toEnum $ fromEnum f):d ) @@ -787,8 +786,7 @@ withParamsPrepared params action = withArray formats $ \fs -> action n vs ls fs where - (values, lengths, formats) = foldl' accum ([],[],[]) $ reverse params - !c_lengths = map toEnum lengths :: [CInt] + (values, c_lengths, formats) = foldl' accum ([],[],[]) $ reverse params !n = toEnum $ length params accum (!a,!b,!c) Nothing = ( Nothing:a @@ -796,7 +794,7 @@ withParamsPrepared params action = , 0:c ) accum (!a,!b,!c) (Just (v, f)) = ( (Just v):a - , (B.length v):b + , (toEnum $ B.length v):b , (toEnum $ fromEnum f):c ) From 325087f03bcd34237b8753e9d21a260a225146d8 Mon Sep 17 00:00:00 2001 From: Robert Vollmert Date: Wed, 29 Jun 2022 13:37:09 +0200 Subject: [PATCH 3/5] withParams*: drop redundant call to 'length' by using 'withArrayLen' We encode various arrays of the same length through 'withArray'. Each of these computes the length, so we might as well take the length from one of these. --- src/Database/PostgreSQL/LibPQ.hsc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Database/PostgreSQL/LibPQ.hsc b/src/Database/PostgreSQL/LibPQ.hsc index dc08473..2ab9f25 100644 --- a/src/Database/PostgreSQL/LibPQ.hsc +++ b/src/Database/PostgreSQL/LibPQ.hsc @@ -756,12 +756,11 @@ withParams params action = withMany (maybeWith B.useAsCString) values $ \c_values -> withArray c_values $ \vs -> withArray c_lengths $ \ls -> - withArray formats $ \fs -> - action n ts vs ls fs + withArrayLen formats $ \n fs -> + action (toEnum n) ts vs ls fs where (oids, values, c_lengths, formats) = foldl' accum ([],[],[],[]) $ reverse params - !n = toEnum $ length params accum (!a,!b,!c,!d) Nothing = ( invalidOid:a , Nothing:b @@ -783,11 +782,10 @@ withParamsPrepared params action = withMany (maybeWith B.useAsCString) values $ \c_values -> withArray c_values $ \vs -> withArray c_lengths $ \ls -> - withArray formats $ \fs -> - action n vs ls fs + withArrayLen formats $ \n fs -> + action (toEnum n) vs ls fs where (values, c_lengths, formats) = foldl' accum ([],[],[]) $ reverse params - !n = toEnum $ length params accum (!a,!b,!c) Nothing = ( Nothing:a , 0:b From ed00f57501f41eb5118da1ac5037f25feaf83ad6 Mon Sep 17 00:00:00 2001 From: Robert Vollmert Date: Wed, 29 Jun 2022 15:36:21 +0200 Subject: [PATCH 4/5] don't copy binary parameter data --- src/Database/PostgreSQL/LibPQ.hsc | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Database/PostgreSQL/LibPQ.hsc b/src/Database/PostgreSQL/LibPQ.hsc index 2ab9f25..7ee632e 100644 --- a/src/Database/PostgreSQL/LibPQ.hsc +++ b/src/Database/PostgreSQL/LibPQ.hsc @@ -747,13 +747,26 @@ invalidOid :: Oid invalidOid = Oid (#const InvalidOid) +-- | Prepare the given parameter bytestring for passing on to libpq, +-- without copying for binary parameters. +-- +-- This safe to use to pass parameters to libpq considering: +-- * libpq treats the parameter data as read-only +-- * 'ByteString' uses pinned memory +-- * the reference to the 'CString' doesn't escape +unsafeUseParamAsCString :: (B.ByteString, Format) -> (CString -> IO a) -> IO a +unsafeUseParamAsCString (bs, format) = + case format of + Binary -> B.unsafeUseAsCString bs + Text -> B.useAsCString bs + -- | Convert a list of parameters to the format expected by libpq FFI calls. withParams :: [Maybe (Oid, B.ByteString, Format)] -> (CInt -> Ptr Oid -> Ptr CString -> Ptr CInt -> Ptr CInt -> IO a) -> IO a withParams params action = withArray oids $ \ts -> - withMany (maybeWith B.useAsCString) values $ \c_values -> + withMany (maybeWith unsafeUseParamAsCString) values $ \c_values -> withArray c_values $ \vs -> withArray c_lengths $ \ls -> withArrayLen formats $ \n fs -> @@ -768,7 +781,7 @@ withParams params action = , 0:d ) accum (!a,!b,!c,!d) (Just (t,v,f)) = ( t:a - , (Just v):b + , (Just (v,f)):b , (toEnum $ B.length v):c , (toEnum $ fromEnum f):d ) @@ -779,7 +792,7 @@ withParamsPrepared :: [Maybe (B.ByteString, Format)] -> (CInt -> Ptr CString -> Ptr CInt -> Ptr CInt -> IO a) -> IO a withParamsPrepared params action = - withMany (maybeWith B.useAsCString) values $ \c_values -> + withMany (maybeWith unsafeUseParamAsCString) values $ \c_values -> withArray c_values $ \vs -> withArray c_lengths $ \ls -> withArrayLen formats $ \n fs -> @@ -791,7 +804,7 @@ withParamsPrepared params action = , 0:b , 0:c ) - accum (!a,!b,!c) (Just (v, f)) = ( (Just v):a + accum (!a,!b,!c) (Just (v, f)) = ( (Just (v,f)):a , (toEnum $ B.length v):b , (toEnum $ fromEnum f):c ) From e7b0be31ff1b6b3207c2b8848adea1d6eaf61171 Mon Sep 17 00:00:00 2001 From: Robert Vollmert Date: Mon, 5 Sep 2022 15:32:25 +0200 Subject: [PATCH 5/5] add a CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 527859d..49dc4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- Avoid copies when passing binary parameters + 0.9.4.3 -------